diff options
242 files changed, 33433 insertions, 0 deletions
diff --git a/org.eclipse.mylyn-feature/.project b/org.eclipse.mylyn-feature/.project new file mode 100644 index 000000000..11fcd6a03 --- /dev/null +++ b/org.eclipse.mylyn-feature/.project @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eclipse.mylar-feature</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.pde.FeatureBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.pde.FeatureNature</nature> + </natures> +</projectDescription> diff --git a/org.eclipse.mylyn-feature/build.properties b/org.eclipse.mylyn-feature/build.properties new file mode 100644 index 000000000..cc2efe007 --- /dev/null +++ b/org.eclipse.mylyn-feature/build.properties @@ -0,0 +1,2 @@ +bin.includes = feature.xml +src.includes = feature.xml diff --git a/org.eclipse.mylyn-feature/epl-v10.html b/org.eclipse.mylyn-feature/epl-v10.html new file mode 100644 index 000000000..ed4b19665 --- /dev/null +++ b/org.eclipse.mylyn-feature/epl-v10.html @@ -0,0 +1,328 @@ +<html xmlns:o="urn:schemas-microsoft-com:office:office" +xmlns:w="urn:schemas-microsoft-com:office:word" +xmlns="http://www.w3.org/TR/REC-html40"> + +<head> +<meta http-equiv=Content-Type content="text/html; charset=windows-1252"> +<meta name=ProgId content=Word.Document> +<meta name=Generator content="Microsoft Word 9"> +<meta name=Originator content="Microsoft Word 9"> +<link rel=File-List +href="./Eclipse%20EPL%202003_11_10%20Final_files/filelist.xml"> +<title>Eclipse Public License - Version 1.0</title> +<!--[if gte mso 9]><xml> + <o:DocumentProperties> + <o:Revision>2</o:Revision> + <o:TotalTime>3</o:TotalTime> + <o:Created>2004-03-05T23:03:00Z</o:Created> + <o:LastSaved>2004-03-05T23:03:00Z</o:LastSaved> + <o:Pages>4</o:Pages> + <o:Words>1626</o:Words> + <o:Characters>9270</o:Characters> + <o:Lines>77</o:Lines> + <o:Paragraphs>18</o:Paragraphs> + <o:CharactersWithSpaces>11384</o:CharactersWithSpaces> + <o:Version>9.4402</o:Version> + </o:DocumentProperties> +</xml><![endif]--><!--[if gte mso 9]><xml> + <w:WordDocument> + <w:TrackRevisions/> + </w:WordDocument> +</xml><![endif]--> +<style> +<!-- + /* Font Definitions */ +@font-face + {font-family:Tahoma; + panose-1:2 11 6 4 3 5 4 4 2 4; + mso-font-charset:0; + mso-generic-font-family:swiss; + mso-font-pitch:variable; + mso-font-signature:553679495 -2147483648 8 0 66047 0;} + /* Style Definitions */ +p.MsoNormal, li.MsoNormal, div.MsoNormal + {mso-style-parent:""; + margin:0in; + margin-bottom:.0001pt; + mso-pagination:widow-orphan; + font-size:12.0pt; + font-family:"Times New Roman"; + mso-fareast-font-family:"Times New Roman";} +p + {margin-right:0in; + mso-margin-top-alt:auto; + mso-margin-bottom-alt:auto; + margin-left:0in; + mso-pagination:widow-orphan; + font-size:12.0pt; + font-family:"Times New Roman"; + mso-fareast-font-family:"Times New Roman";} +p.BalloonText, li.BalloonText, div.BalloonText + {mso-style-name:"Balloon Text"; + margin:0in; + margin-bottom:.0001pt; + mso-pagination:widow-orphan; + font-size:8.0pt; + font-family:Tahoma; + mso-fareast-font-family:"Times New Roman";} +@page Section1 + {size:8.5in 11.0in; + margin:1.0in 1.25in 1.0in 1.25in; + mso-header-margin:.5in; + mso-footer-margin:.5in; + mso-paper-source:0;} +div.Section1 + {page:Section1;} +--> +</style> +</head> + +<body lang=EN-US style='tab-interval:.5in'> + +<div class=Section1> + +<p align=center style='text-align:center'><b>Eclipse Public License - v 1.0</b> +</p> + +<p><span style='font-size:10.0pt'>THE ACCOMPANYING PROGRAM IS PROVIDED UNDER +THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, +REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE +OF THIS AGREEMENT.</span> </p> + +<p><b><span style='font-size:10.0pt'>1. DEFINITIONS</span></b> </p> + +<p><span style='font-size:10.0pt'>"Contribution" means:</span> </p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>a) +in the case of the initial Contributor, the initial code and documentation +distributed under this Agreement, and<br clear=left> +b) in the case of each subsequent Contributor:</span></p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>i) +changes to the Program, and</span></p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>ii) +additions to the Program;</span></p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>where +such changes and/or additions to the Program originate from and are distributed +by that particular Contributor. A Contribution 'originates' from a Contributor +if it was added to the Program by such Contributor itself or anyone acting on +such Contributor's behalf. Contributions do not include additions to the +Program which: (i) are separate modules of software distributed in conjunction +with the Program under their own license agreement, and (ii) are not derivative +works of the Program. </span></p> + +<p><span style='font-size:10.0pt'>"Contributor" means any person or +entity that distributes the Program.</span> </p> + +<p><span style='font-size:10.0pt'>"Licensed Patents " mean patent +claims licensable by a Contributor which are necessarily infringed by the use +or sale of its Contribution alone or when combined with the Program. </span></p> + +<p><span style='font-size:10.0pt'>"Program" means the Contributions +distributed in accordance with this Agreement.</span> </p> + +<p><span style='font-size:10.0pt'>"Recipient" means anyone who +receives the Program under this Agreement, including all Contributors.</span> </p> + +<p><b><span style='font-size:10.0pt'>2. GRANT OF RIGHTS</span></b> </p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>a) +Subject to the terms of this Agreement, each Contributor hereby grants Recipient +a non-exclusive, worldwide, royalty-free copyright license to<span +style='color:red'> </span>reproduce, prepare derivative works of, publicly +display, publicly perform, distribute and sublicense the Contribution of such +Contributor, if any, and such derivative works, in source code and object code +form.</span></p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>b) +Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide,<span style='color:green'> </span>royalty-free +patent license under Licensed Patents to make, use, sell, offer to sell, import +and otherwise transfer the Contribution of such Contributor, if any, in source +code and object code form. This patent license shall apply to the combination +of the Contribution and the Program if, at the time the Contribution is added +by the Contributor, such addition of the Contribution causes such combination +to be covered by the Licensed Patents. The patent license shall not apply to +any other combinations which include the Contribution. No hardware per se is +licensed hereunder. </span></p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>c) +Recipient understands that although each Contributor grants the licenses to its +Contributions set forth herein, no assurances are provided by any Contributor +that the Program does not infringe the patent or other intellectual property +rights of any other entity. Each Contributor disclaims any liability to Recipient +for claims brought by any other entity based on infringement of intellectual +property rights or otherwise. As a condition to exercising the rights and +licenses granted hereunder, each Recipient hereby assumes sole responsibility +to secure any other intellectual property rights needed, if any. For example, +if a third party patent license is required to allow Recipient to distribute +the Program, it is Recipient's responsibility to acquire that license before +distributing the Program.</span></p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>d) +Each Contributor represents that to its knowledge it has sufficient copyright +rights in its Contribution, if any, to grant the copyright license set forth in +this Agreement. </span></p> + +<p><b><span style='font-size:10.0pt'>3. REQUIREMENTS</span></b> </p> + +<p><span style='font-size:10.0pt'>A Contributor may choose to distribute the +Program in object code form under its own license agreement, provided that:</span> +</p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>a) +it complies with the terms and conditions of this Agreement; and</span></p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>b) +its license agreement:</span></p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>i) +effectively disclaims on behalf of all Contributors all warranties and +conditions, express and implied, including warranties or conditions of title +and non-infringement, and implied warranties or conditions of merchantability +and fitness for a particular purpose; </span></p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>ii) +effectively excludes on behalf of all Contributors all liability for damages, +including direct, indirect, special, incidental and consequential damages, such +as lost profits; </span></p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>iii) +states that any provisions which differ from this Agreement are offered by that +Contributor alone and not by any other party; and</span></p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>iv) +states that source code for the Program is available from such Contributor, and +informs licensees how to obtain it in a reasonable manner on or through a +medium customarily used for software exchange.<span style='color:blue'> </span></span></p> + +<p><span style='font-size:10.0pt'>When the Program is made available in source +code form:</span> </p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>a) +it must be made available under this Agreement; and </span></p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>b) a +copy of this Agreement must be included with each copy of the Program. </span></p> + +<p><span style='font-size:10.0pt'>Contributors may not remove or alter any +copyright notices contained within the Program. </span></p> + +<p><span style='font-size:10.0pt'>Each Contributor must identify itself as the +originator of its Contribution, if any, in a manner that reasonably allows +subsequent Recipients to identify the originator of the Contribution. </span></p> + +<p><b><span style='font-size:10.0pt'>4. COMMERCIAL DISTRIBUTION</span></b> </p> + +<p><span style='font-size:10.0pt'>Commercial distributors of software may +accept certain responsibilities with respect to end users, business partners +and the like. While this license is intended to facilitate the commercial use +of the Program, the Contributor who includes the Program in a commercial +product offering should do so in a manner which does not create potential +liability for other Contributors. Therefore, if a Contributor includes the +Program in a commercial product offering, such Contributor ("Commercial +Contributor") hereby agrees to defend and indemnify every other +Contributor ("Indemnified Contributor") against any losses, damages and +costs (collectively "Losses") arising from claims, lawsuits and other +legal actions brought by a third party against the Indemnified Contributor to +the extent caused by the acts or omissions of such Commercial Contributor in +connection with its distribution of the Program in a commercial product +offering. The obligations in this section do not apply to any claims or Losses +relating to any actual or alleged intellectual property infringement. In order +to qualify, an Indemnified Contributor must: a) promptly notify the Commercial +Contributor in writing of such claim, and b) allow the Commercial Contributor +to control, and cooperate with the Commercial Contributor in, the defense and +any related settlement negotiations. The Indemnified Contributor may participate +in any such claim at its own expense.</span> </p> + +<p><span style='font-size:10.0pt'>For example, a Contributor might include the +Program in a commercial product offering, Product X. That Contributor is then a +Commercial Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance claims and +warranties are such Commercial Contributor's responsibility alone. Under this +section, the Commercial Contributor would have to defend claims against the +other Contributors related to those performance claims and warranties, and if a +court requires any other Contributor to pay any damages as a result, the +Commercial Contributor must pay those damages.</span> </p> + +<p><b><span style='font-size:10.0pt'>5. NO WARRANTY</span></b> </p> + +<p><span style='font-size:10.0pt'>EXCEPT AS EXPRESSLY SET FORTH IN THIS +AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, +WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, +MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely +responsible for determining the appropriateness of using and distributing the +Program and assumes all risks associated with its exercise of rights under this +Agreement , including but not limited to the risks and costs of program errors, +compliance with applicable laws, damage to or loss of data, programs or +equipment, and unavailability or interruption of operations. </span></p> + +<p><b><span style='font-size:10.0pt'>6. DISCLAIMER OF LIABILITY</span></b> </p> + +<p><span style='font-size:10.0pt'>EXCEPT AS EXPRESSLY SET FORTH IN THIS +AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF +THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGES.</span> </p> + +<p><b><span style='font-size:10.0pt'>7. GENERAL</span></b> </p> + +<p><span style='font-size:10.0pt'>If any provision of this Agreement is invalid +or unenforceable under applicable law, it shall not affect the validity or +enforceability of the remainder of the terms of this Agreement, and without +further action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable.</span> </p> + +<p><span style='font-size:10.0pt'>If Recipient institutes patent litigation +against any entity (including a cross-claim or counterclaim in a lawsuit) +alleging that the Program itself (excluding combinations of the Program with +other software or hardware) infringes such Recipient's patent(s), then such +Recipient's rights granted under Section 2(b) shall terminate as of the date +such litigation is filed. </span></p> + +<p><span style='font-size:10.0pt'>All Recipient's rights under this Agreement +shall terminate if it fails to comply with any of the material terms or +conditions of this Agreement and does not cure such failure in a reasonable +period of time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use and +distribution of the Program as soon as reasonably practicable. However, +Recipient's obligations under this Agreement and any licenses granted by +Recipient relating to the Program shall continue and survive. </span></p> + +<p><span style='font-size:10.0pt'>Everyone is permitted to copy and distribute +copies of this Agreement, but in order to avoid inconsistency the Agreement is +copyrighted and may only be modified in the following manner. The Agreement +Steward reserves the right to publish new versions (including revisions) of +this Agreement from time to time. No one other than the Agreement Steward has +the right to modify this Agreement. The Eclipse Foundation is the initial +Agreement Steward. The Eclipse Foundation may assign the responsibility to +serve as the Agreement Steward to a suitable separate entity. Each new version +of the Agreement will be given a distinguishing version number. The Program +(including Contributions) may always be distributed subject to the version of +the Agreement under which it was received. In addition, after a new version of +the Agreement is published, Contributor may elect to distribute the Program +(including its Contributions) under the new version. Except as expressly stated +in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to +the intellectual property of any Contributor under this Agreement, whether +expressly, by implication, estoppel or otherwise. All rights in the Program not +expressly granted under this Agreement are reserved.</span> </p> + +<p><span style='font-size:10.0pt'>This Agreement is governed by the laws of the +State of New York and the intellectual property laws of the United States of +America. No party to this Agreement will bring a legal action under this +Agreement more than one year after the cause of action arose. Each party waives +its rights to a jury trial in any resulting litigation.</span> </p> + +<p class=MsoNormal><![if !supportEmptyParas]> <![endif]><o:p></o:p></p> + +</div> + +</body> + +</html>
\ No newline at end of file diff --git a/org.eclipse.mylyn-feature/feature.xml b/org.eclipse.mylyn-feature/feature.xml new file mode 100644 index 000000000..b4ec70b87 --- /dev/null +++ b/org.eclipse.mylyn-feature/feature.xml @@ -0,0 +1,280 @@ +<?xml version="1.0" encoding="UTF-8"?> +<feature + id="org.eclipse.mylar_feature" + label="org.eclipse.mylar-feature" + version="0.2.3" + provider-name="University of British Columbia"> + + <description url="http://www.cs.ubc.ca/~mylar"> + Mylar Plugin + </description> + + <license url="http://www.eclipse.org/legal/epl-v10.html"> + Eclipse Public License - v 1.0 +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS +ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR +DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE +OF THIS AGREEMENT. +1. DEFINITIONS +"Contribution" means: +a) in the case of the initial Contributor, the initial code and +documentation distributed under this Agreement, and +b) in the case of each subsequent Contributor: +i) changes to the Program, and +ii) additions to the Program; +where such changes and/or additions to the Program originate +from and are distributed by that particular Contributor. A Contribution +'originates' from a Contributor if it was added to the Program +by such Contributor itself or anyone acting on such Contributor's +behalf. Contributions do not include additions to the Program +which: (i) are separate modules of software distributed in conjunction +with the Program under their own license agreement, and (ii) +are not derivative works of the Program. +"Contributor" means any person or entity that distributes the +Program. +"Licensed Patents " mean patent claims licensable by a Contributor +which are necessarily infringed by the use or sale of its Contribution +alone or when combined with the Program. +"Program" means the Contributions distributed in accordance with +this Agreement. +"Recipient" means anyone who receives the Program under this +Agreement, including all Contributors. +2. GRANT OF RIGHTS +a) Subject to the terms of this Agreement, each Contributor hereby +grants Recipient a non-exclusive, worldwide, royalty-free copyright +license to reproduce, prepare derivative works of, publicly display, +publicly perform, distribute and sublicense the Contribution +of such Contributor, if any, and such derivative works, in source +code and object code form. +b) Subject to the terms of this Agreement, each Contributor hereby +grants Recipient a non-exclusive, worldwide, royalty-free patent +license under Licensed Patents to make, use, sell, offer to sell, +import and otherwise transfer the Contribution of such Contributor, +if any, in source code and object code form. This patent license +shall apply to the combination of the Contribution and the Program +if, at the time the Contribution is added by the Contributor, +such addition of the Contribution causes such combination to +be covered by the Licensed Patents. The patent license shall +not apply to any other combinations which include the Contribution. +No hardware per se is licensed hereunder. +c) Recipient understands that although each Contributor grants +the licenses to its Contributions set forth herein, no assurances +are provided by any Contributor that the Program does not infringe +the patent or other intellectual property rights of any other +entity. Each Contributor disclaims any liability to Recipient +for claims brought by any other entity based on infringement +of intellectual property rights or otherwise. As a condition +to exercising the rights and licenses granted hereunder, each +Recipient hereby assumes sole responsibility to secure any other +intellectual property rights needed, if any. For example, if +a third party patent license is required to allow Recipient to +distribute the Program, it is Recipient's responsibility to acquire +that license before distributing the Program. +d) Each Contributor represents that to its knowledge it has sufficient +copyright rights in its Contribution, if any, to grant the copyright +license set forth in this Agreement. +3. REQUIREMENTS +A Contributor may choose to distribute the Program in object +code form under its own license agreement, provided that: +a) it complies with the terms and conditions of this Agreement; +and +b) its license agreement: +i) effectively disclaims on behalf of all Contributors all warranties +and conditions, express and implied, including warranties or +conditions of title and non-infringement, and implied warranties +or conditions of merchantability and fitness for a particular +purpose; +ii) effectively excludes on behalf of all Contributors all liability +for damages, including direct, indirect, special, incidental +and consequential damages, such as lost profits; +iii) states that any provisions which differ from this Agreement +are offered by that Contributor alone and not by any other party; +and +iv) states that source code for the Program is available from +such Contributor, and informs licensees how to obtain it in a +reasonable manner on or through a medium customarily used for +software exchange. +When the Program is made available in source code form: +a) it must be made available under this Agreement; and +b) a copy of this Agreement must be included with each copy of +the Program. +Contributors may not remove or alter any copyright notices contained +within the Program. +Each Contributor must identify itself as the originator of its +Contribution, if any, in a manner that reasonably allows subsequent +Recipients to identify the originator of the Contribution. +4. COMMERCIAL DISTRIBUTION +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While +this license is intended to facilitate the commercial use of +the Program, the Contributor who includes the Program in a commercial +product offering should do so in a manner which does not create +potential liability for other Contributors. Therefore, if a Contributor +includes the Program in a commercial product offering, such Contributor +("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any +losses, damages and costs (collectively "Losses") arising from +claims, lawsuits and other legal actions brought by a third party +against the Indemnified Contributor to the extent caused by the +acts or omissions of such Commercial Contributor in connection +with its distribution of the Program in a commercial product +offering. The obligations in this section do not apply to any +claims or Losses relating to any actual or alleged intellectual +property infringement. In order to qualify, an Indemnified Contributor +must: a) promptly notify the Commercial Contributor in writing +of such claim, and b) allow the Commercial Contributor to control, +and cooperate with the Commercial Contributor in, the defense +and any related settlement negotiations. The Indemnified Contributor +may participate in any such claim at its own expense. +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have +to defend claims against the other Contributors related to those +performance claims and warranties, and if a court requires any +other Contributor to pay any damages as a result, the Commercial +Contributor must pay those damages. +5. NO WARRANTY +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM +IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS +OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, +ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY +OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely +responsible for determining the appropriateness of using and +distributing the Program and assumes all risks associated with +its exercise of rights under this Agreement , including but not +limited to the risks and costs of program errors, compliance +with applicable laws, damage to or loss of data, programs or +equipment, and unavailability or interruption of operations. +6. DISCLAIMER OF LIABILITY +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT +NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE +OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGES. +7. GENERAL +If any provision of this Agreement is invalid or unenforceable +under applicable law, it shall not affect the validity or enforceability +of the remainder of the terms of this Agreement, and without +further action by the parties hereto, such provision shall be +reformed to the minimum extent necessary to make such provision +valid and enforceable. +If Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging +that the Program itself (excluding combinations of the Program +with other software or hardware) infringes such Recipient's patent(s), +then such Recipient's rights granted under Section 2(b) shall +terminate as of the date such litigation is filed. +All Recipient's rights under this Agreement shall terminate if +it fails to comply with any of the material terms or conditions +of this Agreement and does not cure such failure in a reasonable +period of time after becoming aware of such noncompliance. If +all Recipient's rights under this Agreement terminate, Recipient +agrees to cease use and distribution of the Program as soon as +reasonably practicable. However, Recipient's obligations under +this Agreement and any licenses granted by Recipient relating +to the Program shall continue and survive. +Everyone is permitted to copy and distribute copies of this Agreement, +but in order to avoid inconsistency the Agreement is copyrighted +and may only be modified in the following manner. The Agreement +Steward reserves the right to publish new versions (including +revisions) of this Agreement from time to time. No one other +than the Agreement Steward has the right to modify this Agreement. +The Eclipse Foundation is the initial Agreement Steward. The +Eclipse Foundation may assign the responsibility to serve as +the Agreement Steward to a suitable separate entity. Each new +version of the Agreement will be given a distinguishing version +number. The Program (including Contributions) may always be distributed +subject to the version of the Agreement under which it was received. +In addition, after a new version of the Agreement is published, +Contributor may elect to distribute the Program (including its +Contributions) under the new version. Except as expressly stated +in Sections 2(a) and 2(b) above, Recipient receives no rights +or licenses to the intellectual property of any Contributor under +this Agreement, whether expressly, by implication, estoppel or +otherwise. All rights in the Program not expressly granted under +this Agreement are reserved. +This Agreement is governed by the laws of the State of New York +and the intellectual property laws of the United States of America. +No party to this Agreement will bring a legal action under this +Agreement more than one year after the cause of action arose. +Each party waives its rights to a jury trial in any resulting +litigation. + </license> + + <includes + id="org.eclipse.mylar.bugzilla_feature" + version="1.7.2"/> + + <includes + id="org.eclipse.mylar.monitor_feature" + version="0.2.3"/> + + <requires> + <import plugin="org.eclipse.ui" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.core.runtime" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.core.resources" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.jdt" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.jdt.core" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.jdt.ui" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.ui.editors" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.jface.text" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.ui.workbench.texteditor" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.search" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.ui.views" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.ui.ide" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.ui.console" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.ui.forms" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.osgi.util" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.osgi.services" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.pde.ui" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.ant.ui" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.ant.core" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.pde.core" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.mylar.core"/> + <import plugin="org.eclipse.mylar.bugzilla"/> + </requires> + + <plugin + id="org.eclipse.mylar.doc" + download-size="0" + install-size="0" + version="0.2.3"/> + + <plugin + id="org.eclipse.mylar.hypertext" + download-size="0" + install-size="0" + version="0.2.3"/> + + <plugin + id="org.eclipse.mylar.java" + download-size="0" + install-size="0" + version="0.2.3"/> + + <plugin + id="org.eclipse.mylar.ui" + download-size="0" + install-size="0" + version="0.2.3"/> + + <plugin + id="org.eclipse.mylar.xml" + download-size="0" + install-size="0" + version="0.2.3"/> + + <plugin + id="org.eclipse.mylar.tasks" + download-size="0" + install-size="0" + version="0.2.3"/> + +</feature> diff --git a/org.eclipse.mylyn-feature/license.html b/org.eclipse.mylyn-feature/license.html new file mode 100644 index 000000000..3259fb996 --- /dev/null +++ b/org.eclipse.mylyn-feature/license.html @@ -0,0 +1,73 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN"> +<html> +<head> +<meta http-equiv=Content-Type content="text/html; charset=iso-8859-1"> +<title>Eclipse.org Software User Agreement</title> +</head> + +<body lang="EN-US" link=blue vlink=purple> +<h2>Eclipse Foundation Software User Agreement</h2> +<p>January 28, 2005</p> + +<h3>Usage Of Content</h3> + +<p>THE ECLIPSE FOUNDATION MAKES AVAILABLE SOFTWARE, DOCUMENTATION, INFORMATION AND/OR OTHER MATERIALS FOR OPEN SOURCE PROJECTS + (COLLECTIVELY "CONTENT"). USE OF THE CONTENT IS GOVERNED BY THE TERMS AND CONDITIONS OF THIS AGREEMENT AND/OR THE TERMS AND + CONDITIONS OF LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW. BY USING THE CONTENT, YOU AGREE THAT YOUR USE + OF THE CONTENT IS GOVERNED BY THIS AGREEMENT AND/OR THE TERMS AND CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR + NOTICES INDICATED OR REFERENCED BELOW. IF YOU DO NOT AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT AND THE TERMS AND + CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW, THEN YOU MAY NOT USE THE CONTENT.</p> + +<h3>Applicable Licenses</h3> + +<p>Unless otherwise indicated, all Content made available by the Eclipse Foundation is provided to you under the terms and conditions of the Eclipse Public License Version 1.0 + ("EPL"). A copy of the EPL is provided with this Content and is also available at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>. + For purposes of the EPL, "Program" will mean the Content.</p> + +<p>Content includes, but is not limited to, source code, object code, documentation and other files maintained in the Eclipse.org CVS repository ("Repository") in CVS + modules ("Modules") and made available as downloadable archives ("Downloads").</p> + +<p>Content may be apportioned into plug-ins ("Plug-ins"), plug-in fragments ("Fragments"), and features ("Features"). A Feature is a bundle of one or more Plug-ins and/or Fragments and associated material. Files named "feature.xml" may contain a list of the names and version numbers of the Plug-ins and/or Fragments associated with a Feature. Plug-ins and Fragments are located in directories + named "plugins" and Features are located in directories named "features".</p> + +<p>Features may also include other Features ("Included Features"). Files named "feature.xml" may contain a list of the names and version numbers of Included Features.</p> + +<p>The terms and conditions governing Plug-ins and Fragments should be contained in files named "about.html" ("Abouts"). The terms and conditions governing Features and +Included Features should be contained in files named "license.html" ("Feature Licenses"). Abouts and Feature Licenses may be located in any directory of a Download or Module +including, but not limited to the following locations:</p> + +<ul> + <li>The top-level (root) directory</li> + <li>Plug-in and Fragment directories</li> + <li>Subdirectories of the directory named "src" of certain Plug-ins</li> + <li>Feature directories</li> +</ul> + +<p>Note: if a Feature made available by the Eclipse Foundation is installed using the Eclipse Update Manager, you must agree to a license ("Feature Update License") during the +installation process. If the Feature contains Included Features, the Feature Update License should either provide you with the terms and conditions governing the Included Features or +inform you where you can locate them. Feature Update Licenses may be found in the "license" property of files named "feature.properties". +Such Abouts, Feature Licenses and Feature Update Licenses contain the terms and conditions (or references to such terms and conditions) that govern your use of the associated Content in +that directory.</p> + +<p>THE ABOUTS, FEATURE LICENSES AND FEATURE UPDATE LICENSES MAY REFER TO THE EPL OR OTHER LICENSE AGREEMENTS, NOTICES OR TERMS AND CONDITIONS. SOME OF THESE +OTHER LICENSE AGREEMENTS MAY INCLUDE (BUT ARE NOT LIMITED TO):</p> + +<ul> + <li>Common Public License Version 1.0 (available at <a href="http://www.eclipse.org/legal/cpl-v10.html">http://www.eclipse.org/legal/cpl-v10.html</a>)</li> + <li>Apache Software License 1.1 (available at <a href="http://www.apache.org/licenses/LICENSE">http://www.apache.org/licenses/LICENSE</a>)</li> + <li>Apache Software License 2.0 (available at <a href="http://www.apache.org/licenses/LICENSE-2.0">http://www.apache.org/licenses/LICENSE-2.0</a>)</li> + <li>IBM Public License 1.0 (available at <a href="http://oss.software.ibm.com/developerworks/opensource/license10.html">http://oss.software.ibm.com/developerworks/opensource/license10.html</a>)</li> + <li>Metro Link Public License 1.00 (available at <a href="http://www.opengroup.org/openmotif/supporters/metrolink/license.html">http://www.opengroup.org/openmotif/supporters/metrolink/license.html</a>)</li> + <li>Mozilla Public License Version 1.1 (available at <a href="http://www.mozilla.org/MPL/MPL-1.1.html">http://www.mozilla.org/MPL/MPL-1.1.html</a>)</li> +</ul> + +<p>IT IS YOUR OBLIGATION TO READ AND ACCEPT ALL SUCH TERMS AND CONDITIONS PRIOR TO USE OF THE CONTENT. If no About, Feature License or Feature Update License is provided, please +contact the Eclipse Foundation to determine what terms and conditions govern that particular Content.</p> + +<h3>Cryptography</h3> + +<p>Content may contain encryption software. The country in which you are currently may have restrictions on the import, possession, and use, and/or re-export to + another country, of encryption software. BEFORE using any encryption software, please check the country's laws, regulations and policies concerning the import, + possession, or use, and re-export of encryption software, to see if this is permitted.</p> +</body> +</html> diff --git a/org.eclipse.mylyn.bugzilla-feature/.project b/org.eclipse.mylyn.bugzilla-feature/.project new file mode 100644 index 000000000..72dd862cd --- /dev/null +++ b/org.eclipse.mylyn.bugzilla-feature/.project @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eclipse.mylar.bugzilla-feature</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.pde.FeatureBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.pde.FeatureNature</nature> + </natures> +</projectDescription> diff --git a/org.eclipse.mylyn.bugzilla-feature/build.properties b/org.eclipse.mylyn.bugzilla-feature/build.properties new file mode 100644 index 000000000..bbd9708de --- /dev/null +++ b/org.eclipse.mylyn.bugzilla-feature/build.properties @@ -0,0 +1,6 @@ +bin.includes = feature.xml,\ + license.html,\ + epl-v10.html +src.includes = epl-v10.html,\ + feature.xml,\ + license.html diff --git a/org.eclipse.mylyn.bugzilla-feature/epl-v10.html b/org.eclipse.mylyn.bugzilla-feature/epl-v10.html new file mode 100644 index 000000000..ed4b19665 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla-feature/epl-v10.html @@ -0,0 +1,328 @@ +<html xmlns:o="urn:schemas-microsoft-com:office:office" +xmlns:w="urn:schemas-microsoft-com:office:word" +xmlns="http://www.w3.org/TR/REC-html40"> + +<head> +<meta http-equiv=Content-Type content="text/html; charset=windows-1252"> +<meta name=ProgId content=Word.Document> +<meta name=Generator content="Microsoft Word 9"> +<meta name=Originator content="Microsoft Word 9"> +<link rel=File-List +href="./Eclipse%20EPL%202003_11_10%20Final_files/filelist.xml"> +<title>Eclipse Public License - Version 1.0</title> +<!--[if gte mso 9]><xml> + <o:DocumentProperties> + <o:Revision>2</o:Revision> + <o:TotalTime>3</o:TotalTime> + <o:Created>2004-03-05T23:03:00Z</o:Created> + <o:LastSaved>2004-03-05T23:03:00Z</o:LastSaved> + <o:Pages>4</o:Pages> + <o:Words>1626</o:Words> + <o:Characters>9270</o:Characters> + <o:Lines>77</o:Lines> + <o:Paragraphs>18</o:Paragraphs> + <o:CharactersWithSpaces>11384</o:CharactersWithSpaces> + <o:Version>9.4402</o:Version> + </o:DocumentProperties> +</xml><![endif]--><!--[if gte mso 9]><xml> + <w:WordDocument> + <w:TrackRevisions/> + </w:WordDocument> +</xml><![endif]--> +<style> +<!-- + /* Font Definitions */ +@font-face + {font-family:Tahoma; + panose-1:2 11 6 4 3 5 4 4 2 4; + mso-font-charset:0; + mso-generic-font-family:swiss; + mso-font-pitch:variable; + mso-font-signature:553679495 -2147483648 8 0 66047 0;} + /* Style Definitions */ +p.MsoNormal, li.MsoNormal, div.MsoNormal + {mso-style-parent:""; + margin:0in; + margin-bottom:.0001pt; + mso-pagination:widow-orphan; + font-size:12.0pt; + font-family:"Times New Roman"; + mso-fareast-font-family:"Times New Roman";} +p + {margin-right:0in; + mso-margin-top-alt:auto; + mso-margin-bottom-alt:auto; + margin-left:0in; + mso-pagination:widow-orphan; + font-size:12.0pt; + font-family:"Times New Roman"; + mso-fareast-font-family:"Times New Roman";} +p.BalloonText, li.BalloonText, div.BalloonText + {mso-style-name:"Balloon Text"; + margin:0in; + margin-bottom:.0001pt; + mso-pagination:widow-orphan; + font-size:8.0pt; + font-family:Tahoma; + mso-fareast-font-family:"Times New Roman";} +@page Section1 + {size:8.5in 11.0in; + margin:1.0in 1.25in 1.0in 1.25in; + mso-header-margin:.5in; + mso-footer-margin:.5in; + mso-paper-source:0;} +div.Section1 + {page:Section1;} +--> +</style> +</head> + +<body lang=EN-US style='tab-interval:.5in'> + +<div class=Section1> + +<p align=center style='text-align:center'><b>Eclipse Public License - v 1.0</b> +</p> + +<p><span style='font-size:10.0pt'>THE ACCOMPANYING PROGRAM IS PROVIDED UNDER +THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, +REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE +OF THIS AGREEMENT.</span> </p> + +<p><b><span style='font-size:10.0pt'>1. DEFINITIONS</span></b> </p> + +<p><span style='font-size:10.0pt'>"Contribution" means:</span> </p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>a) +in the case of the initial Contributor, the initial code and documentation +distributed under this Agreement, and<br clear=left> +b) in the case of each subsequent Contributor:</span></p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>i) +changes to the Program, and</span></p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>ii) +additions to the Program;</span></p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>where +such changes and/or additions to the Program originate from and are distributed +by that particular Contributor. A Contribution 'originates' from a Contributor +if it was added to the Program by such Contributor itself or anyone acting on +such Contributor's behalf. Contributions do not include additions to the +Program which: (i) are separate modules of software distributed in conjunction +with the Program under their own license agreement, and (ii) are not derivative +works of the Program. </span></p> + +<p><span style='font-size:10.0pt'>"Contributor" means any person or +entity that distributes the Program.</span> </p> + +<p><span style='font-size:10.0pt'>"Licensed Patents " mean patent +claims licensable by a Contributor which are necessarily infringed by the use +or sale of its Contribution alone or when combined with the Program. </span></p> + +<p><span style='font-size:10.0pt'>"Program" means the Contributions +distributed in accordance with this Agreement.</span> </p> + +<p><span style='font-size:10.0pt'>"Recipient" means anyone who +receives the Program under this Agreement, including all Contributors.</span> </p> + +<p><b><span style='font-size:10.0pt'>2. GRANT OF RIGHTS</span></b> </p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>a) +Subject to the terms of this Agreement, each Contributor hereby grants Recipient +a non-exclusive, worldwide, royalty-free copyright license to<span +style='color:red'> </span>reproduce, prepare derivative works of, publicly +display, publicly perform, distribute and sublicense the Contribution of such +Contributor, if any, and such derivative works, in source code and object code +form.</span></p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>b) +Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide,<span style='color:green'> </span>royalty-free +patent license under Licensed Patents to make, use, sell, offer to sell, import +and otherwise transfer the Contribution of such Contributor, if any, in source +code and object code form. This patent license shall apply to the combination +of the Contribution and the Program if, at the time the Contribution is added +by the Contributor, such addition of the Contribution causes such combination +to be covered by the Licensed Patents. The patent license shall not apply to +any other combinations which include the Contribution. No hardware per se is +licensed hereunder. </span></p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>c) +Recipient understands that although each Contributor grants the licenses to its +Contributions set forth herein, no assurances are provided by any Contributor +that the Program does not infringe the patent or other intellectual property +rights of any other entity. Each Contributor disclaims any liability to Recipient +for claims brought by any other entity based on infringement of intellectual +property rights or otherwise. As a condition to exercising the rights and +licenses granted hereunder, each Recipient hereby assumes sole responsibility +to secure any other intellectual property rights needed, if any. For example, +if a third party patent license is required to allow Recipient to distribute +the Program, it is Recipient's responsibility to acquire that license before +distributing the Program.</span></p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>d) +Each Contributor represents that to its knowledge it has sufficient copyright +rights in its Contribution, if any, to grant the copyright license set forth in +this Agreement. </span></p> + +<p><b><span style='font-size:10.0pt'>3. REQUIREMENTS</span></b> </p> + +<p><span style='font-size:10.0pt'>A Contributor may choose to distribute the +Program in object code form under its own license agreement, provided that:</span> +</p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>a) +it complies with the terms and conditions of this Agreement; and</span></p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>b) +its license agreement:</span></p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>i) +effectively disclaims on behalf of all Contributors all warranties and +conditions, express and implied, including warranties or conditions of title +and non-infringement, and implied warranties or conditions of merchantability +and fitness for a particular purpose; </span></p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>ii) +effectively excludes on behalf of all Contributors all liability for damages, +including direct, indirect, special, incidental and consequential damages, such +as lost profits; </span></p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>iii) +states that any provisions which differ from this Agreement are offered by that +Contributor alone and not by any other party; and</span></p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>iv) +states that source code for the Program is available from such Contributor, and +informs licensees how to obtain it in a reasonable manner on or through a +medium customarily used for software exchange.<span style='color:blue'> </span></span></p> + +<p><span style='font-size:10.0pt'>When the Program is made available in source +code form:</span> </p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>a) +it must be made available under this Agreement; and </span></p> + +<p class=MsoNormal style='margin-left:.5in'><span style='font-size:10.0pt'>b) a +copy of this Agreement must be included with each copy of the Program. </span></p> + +<p><span style='font-size:10.0pt'>Contributors may not remove or alter any +copyright notices contained within the Program. </span></p> + +<p><span style='font-size:10.0pt'>Each Contributor must identify itself as the +originator of its Contribution, if any, in a manner that reasonably allows +subsequent Recipients to identify the originator of the Contribution. </span></p> + +<p><b><span style='font-size:10.0pt'>4. COMMERCIAL DISTRIBUTION</span></b> </p> + +<p><span style='font-size:10.0pt'>Commercial distributors of software may +accept certain responsibilities with respect to end users, business partners +and the like. While this license is intended to facilitate the commercial use +of the Program, the Contributor who includes the Program in a commercial +product offering should do so in a manner which does not create potential +liability for other Contributors. Therefore, if a Contributor includes the +Program in a commercial product offering, such Contributor ("Commercial +Contributor") hereby agrees to defend and indemnify every other +Contributor ("Indemnified Contributor") against any losses, damages and +costs (collectively "Losses") arising from claims, lawsuits and other +legal actions brought by a third party against the Indemnified Contributor to +the extent caused by the acts or omissions of such Commercial Contributor in +connection with its distribution of the Program in a commercial product +offering. The obligations in this section do not apply to any claims or Losses +relating to any actual or alleged intellectual property infringement. In order +to qualify, an Indemnified Contributor must: a) promptly notify the Commercial +Contributor in writing of such claim, and b) allow the Commercial Contributor +to control, and cooperate with the Commercial Contributor in, the defense and +any related settlement negotiations. The Indemnified Contributor may participate +in any such claim at its own expense.</span> </p> + +<p><span style='font-size:10.0pt'>For example, a Contributor might include the +Program in a commercial product offering, Product X. That Contributor is then a +Commercial Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance claims and +warranties are such Commercial Contributor's responsibility alone. Under this +section, the Commercial Contributor would have to defend claims against the +other Contributors related to those performance claims and warranties, and if a +court requires any other Contributor to pay any damages as a result, the +Commercial Contributor must pay those damages.</span> </p> + +<p><b><span style='font-size:10.0pt'>5. NO WARRANTY</span></b> </p> + +<p><span style='font-size:10.0pt'>EXCEPT AS EXPRESSLY SET FORTH IN THIS +AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, +WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, +MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely +responsible for determining the appropriateness of using and distributing the +Program and assumes all risks associated with its exercise of rights under this +Agreement , including but not limited to the risks and costs of program errors, +compliance with applicable laws, damage to or loss of data, programs or +equipment, and unavailability or interruption of operations. </span></p> + +<p><b><span style='font-size:10.0pt'>6. DISCLAIMER OF LIABILITY</span></b> </p> + +<p><span style='font-size:10.0pt'>EXCEPT AS EXPRESSLY SET FORTH IN THIS +AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY +OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF +THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGES.</span> </p> + +<p><b><span style='font-size:10.0pt'>7. GENERAL</span></b> </p> + +<p><span style='font-size:10.0pt'>If any provision of this Agreement is invalid +or unenforceable under applicable law, it shall not affect the validity or +enforceability of the remainder of the terms of this Agreement, and without +further action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable.</span> </p> + +<p><span style='font-size:10.0pt'>If Recipient institutes patent litigation +against any entity (including a cross-claim or counterclaim in a lawsuit) +alleging that the Program itself (excluding combinations of the Program with +other software or hardware) infringes such Recipient's patent(s), then such +Recipient's rights granted under Section 2(b) shall terminate as of the date +such litigation is filed. </span></p> + +<p><span style='font-size:10.0pt'>All Recipient's rights under this Agreement +shall terminate if it fails to comply with any of the material terms or +conditions of this Agreement and does not cure such failure in a reasonable +period of time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use and +distribution of the Program as soon as reasonably practicable. However, +Recipient's obligations under this Agreement and any licenses granted by +Recipient relating to the Program shall continue and survive. </span></p> + +<p><span style='font-size:10.0pt'>Everyone is permitted to copy and distribute +copies of this Agreement, but in order to avoid inconsistency the Agreement is +copyrighted and may only be modified in the following manner. The Agreement +Steward reserves the right to publish new versions (including revisions) of +this Agreement from time to time. No one other than the Agreement Steward has +the right to modify this Agreement. The Eclipse Foundation is the initial +Agreement Steward. The Eclipse Foundation may assign the responsibility to +serve as the Agreement Steward to a suitable separate entity. Each new version +of the Agreement will be given a distinguishing version number. The Program +(including Contributions) may always be distributed subject to the version of +the Agreement under which it was received. In addition, after a new version of +the Agreement is published, Contributor may elect to distribute the Program +(including its Contributions) under the new version. Except as expressly stated +in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to +the intellectual property of any Contributor under this Agreement, whether +expressly, by implication, estoppel or otherwise. All rights in the Program not +expressly granted under this Agreement are reserved.</span> </p> + +<p><span style='font-size:10.0pt'>This Agreement is governed by the laws of the +State of New York and the intellectual property laws of the United States of +America. No party to this Agreement will bring a legal action under this +Agreement more than one year after the cause of action arose. Each party waives +its rights to a jury trial in any resulting litigation.</span> </p> + +<p class=MsoNormal><![if !supportEmptyParas]> <![endif]><o:p></o:p></p> + +</div> + +</body> + +</html>
\ No newline at end of file diff --git a/org.eclipse.mylyn.bugzilla-feature/feature.xml b/org.eclipse.mylyn.bugzilla-feature/feature.xml new file mode 100644 index 000000000..009191815 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla-feature/feature.xml @@ -0,0 +1,238 @@ +<?xml version="1.0" encoding="UTF-8"?> +<feature + id="org.eclipse.mylar.bugzilla_feature" + label="org.eclipse.mylar.bugzilla-feature" + version="1.7.2" + provider-name="University of British Columbia"> + + <description> + Bugzilla Client Plugin + </description> + + <license url="http://www.eclipse.org/legal/epl-v10.html"> + Eclipse Public License - v 1.0 +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS +ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR +DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE +OF THIS AGREEMENT. +1. DEFINITIONS +"Contribution" means: +a) in the case of the initial Contributor, the initial code and +documentation distributed under this Agreement, and +b) in the case of each subsequent Contributor: +i) changes to the Program, and +ii) additions to the Program; +where such changes and/or additions to the Program originate +from and are distributed by that particular Contributor. A Contribution +'originates' from a Contributor if it was added to the Program +by such Contributor itself or anyone acting on such Contributor's +behalf. Contributions do not include additions to the Program +which: (i) are separate modules of software distributed in conjunction +with the Program under their own license agreement, and (ii) +are not derivative works of the Program. +"Contributor" means any person or entity that distributes the +Program. +"Licensed Patents " mean patent claims licensable by a Contributor +which are necessarily infringed by the use or sale of its Contribution +alone or when combined with the Program. +"Program" means the Contributions distributed in accordance with +this Agreement. +"Recipient" means anyone who receives the Program under this +Agreement, including all Contributors. +2. GRANT OF RIGHTS +a) Subject to the terms of this Agreement, each Contributor hereby +grants Recipient a non-exclusive, worldwide, royalty-free copyright +license to reproduce, prepare derivative works of, publicly display, +publicly perform, distribute and sublicense the Contribution +of such Contributor, if any, and such derivative works, in source +code and object code form. +b) Subject to the terms of this Agreement, each Contributor hereby +grants Recipient a non-exclusive, worldwide, royalty-free patent +license under Licensed Patents to make, use, sell, offer to sell, +import and otherwise transfer the Contribution of such Contributor, +if any, in source code and object code form. This patent license +shall apply to the combination of the Contribution and the Program +if, at the time the Contribution is added by the Contributor, +such addition of the Contribution causes such combination to +be covered by the Licensed Patents. The patent license shall +not apply to any other combinations which include the Contribution. +No hardware per se is licensed hereunder. +c) Recipient understands that although each Contributor grants +the licenses to its Contributions set forth herein, no assurances +are provided by any Contributor that the Program does not infringe +the patent or other intellectual property rights of any other +entity. Each Contributor disclaims any liability to Recipient +for claims brought by any other entity based on infringement +of intellectual property rights or otherwise. As a condition +to exercising the rights and licenses granted hereunder, each +Recipient hereby assumes sole responsibility to secure any other +intellectual property rights needed, if any. For example, if +a third party patent license is required to allow Recipient to +distribute the Program, it is Recipient's responsibility to acquire +that license before distributing the Program. +d) Each Contributor represents that to its knowledge it has sufficient +copyright rights in its Contribution, if any, to grant the copyright +license set forth in this Agreement. +3. REQUIREMENTS +A Contributor may choose to distribute the Program in object +code form under its own license agreement, provided that: +a) it complies with the terms and conditions of this Agreement; +and +b) its license agreement: +i) effectively disclaims on behalf of all Contributors all warranties +and conditions, express and implied, including warranties or +conditions of title and non-infringement, and implied warranties +or conditions of merchantability and fitness for a particular +purpose; +ii) effectively excludes on behalf of all Contributors all liability +for damages, including direct, indirect, special, incidental +and consequential damages, such as lost profits; +iii) states that any provisions which differ from this Agreement +are offered by that Contributor alone and not by any other party; +and +iv) states that source code for the Program is available from +such Contributor, and informs licensees how to obtain it in a +reasonable manner on or through a medium customarily used for +software exchange. +When the Program is made available in source code form: +a) it must be made available under this Agreement; and +b) a copy of this Agreement must be included with each copy of +the Program. +Contributors may not remove or alter any copyright notices contained +within the Program. +Each Contributor must identify itself as the originator of its +Contribution, if any, in a manner that reasonably allows subsequent +Recipients to identify the originator of the Contribution. +4. COMMERCIAL DISTRIBUTION +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While +this license is intended to facilitate the commercial use of +the Program, the Contributor who includes the Program in a commercial +product offering should do so in a manner which does not create +potential liability for other Contributors. Therefore, if a Contributor +includes the Program in a commercial product offering, such Contributor +("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any +losses, damages and costs (collectively "Losses") arising from +claims, lawsuits and other legal actions brought by a third party +against the Indemnified Contributor to the extent caused by the +acts or omissions of such Commercial Contributor in connection +with its distribution of the Program in a commercial product +offering. The obligations in this section do not apply to any +claims or Losses relating to any actual or alleged intellectual +property infringement. In order to qualify, an Indemnified Contributor +must: a) promptly notify the Commercial Contributor in writing +of such claim, and b) allow the Commercial Contributor to control, +and cooperate with the Commercial Contributor in, the defense +and any related settlement negotiations. The Indemnified Contributor +may participate in any such claim at its own expense. +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have +to defend claims against the other Contributors related to those +performance claims and warranties, and if a court requires any +other Contributor to pay any damages as a result, the Commercial +Contributor must pay those damages. +5. NO WARRANTY +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM +IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS +OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, +ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY +OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely +responsible for determining the appropriateness of using and +distributing the Program and assumes all risks associated with +its exercise of rights under this Agreement , including but not +limited to the risks and costs of program errors, compliance +with applicable laws, damage to or loss of data, programs or +equipment, and unavailability or interruption of operations. +6. DISCLAIMER OF LIABILITY +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT +NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE +OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGES. +7. GENERAL +If any provision of this Agreement is invalid or unenforceable +under applicable law, it shall not affect the validity or enforceability +of the remainder of the terms of this Agreement, and without +further action by the parties hereto, such provision shall be +reformed to the minimum extent necessary to make such provision +valid and enforceable. +If Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging +that the Program itself (excluding combinations of the Program +with other software or hardware) infringes such Recipient's patent(s), +then such Recipient's rights granted under Section 2(b) shall +terminate as of the date such litigation is filed. +All Recipient's rights under this Agreement shall terminate if +it fails to comply with any of the material terms or conditions +of this Agreement and does not cure such failure in a reasonable +period of time after becoming aware of such noncompliance. If +all Recipient's rights under this Agreement terminate, Recipient +agrees to cease use and distribution of the Program as soon as +reasonably practicable. However, Recipient's obligations under +this Agreement and any licenses granted by Recipient relating +to the Program shall continue and survive. +Everyone is permitted to copy and distribute copies of this Agreement, +but in order to avoid inconsistency the Agreement is copyrighted +and may only be modified in the following manner. The Agreement +Steward reserves the right to publish new versions (including +revisions) of this Agreement from time to time. No one other +than the Agreement Steward has the right to modify this Agreement. +The Eclipse Foundation is the initial Agreement Steward. The +Eclipse Foundation may assign the responsibility to serve as +the Agreement Steward to a suitable separate entity. Each new +version of the Agreement will be given a distinguishing version +number. The Program (including Contributions) may always be distributed +subject to the version of the Agreement under which it was received. +In addition, after a new version of the Agreement is published, +Contributor may elect to distribute the Program (including its +Contributions) under the new version. Except as expressly stated +in Sections 2(a) and 2(b) above, Recipient receives no rights +or licenses to the intellectual property of any Contributor under +this Agreement, whether expressly, by implication, estoppel or +otherwise. All rights in the Program not expressly granted under +this Agreement are reserved. +This Agreement is governed by the laws of the State of New York +and the intellectual property laws of the United States of America. +No party to this Agreement will bring a legal action under this +Agreement more than one year after the cause of action arose. +Each party waives its rights to a jury trial in any resulting +litigation. + </license> + + <url> + <update label="Bugzilla update site at UBC Software Practices Lab" url="http://www.cs.ubc.ca/labs/spl/projects/hipikat/updates"/> + <discovery label="Bugzilla update site at UBC Software Practices Lab" url="http://www.cs.ubc.ca/labs/spl/projects/hipikat/updates"/> + </url> + + <requires> + <import plugin="org.eclipse.core.resources" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.ui" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.search" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.help" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.help.ui" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.core.runtime" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.ui.ide" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.ui.views" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.jface.text" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.ui.workbench.texteditor" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.ui.editors" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.pde.ui" version="0.0.0" match="greaterOrEqual"/> + <import plugin="org.eclipse.compare" version="0.0.0" match="greaterOrEqual"/> + </requires> + + <plugin + id="org.eclipse.mylar.bugzilla" + download-size="0" + install-size="0" + version="0.0.0"/> + +</feature> diff --git a/org.eclipse.mylyn.bugzilla-feature/license.html b/org.eclipse.mylyn.bugzilla-feature/license.html new file mode 100644 index 000000000..3259fb996 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla-feature/license.html @@ -0,0 +1,73 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN"> +<html> +<head> +<meta http-equiv=Content-Type content="text/html; charset=iso-8859-1"> +<title>Eclipse.org Software User Agreement</title> +</head> + +<body lang="EN-US" link=blue vlink=purple> +<h2>Eclipse Foundation Software User Agreement</h2> +<p>January 28, 2005</p> + +<h3>Usage Of Content</h3> + +<p>THE ECLIPSE FOUNDATION MAKES AVAILABLE SOFTWARE, DOCUMENTATION, INFORMATION AND/OR OTHER MATERIALS FOR OPEN SOURCE PROJECTS + (COLLECTIVELY "CONTENT"). USE OF THE CONTENT IS GOVERNED BY THE TERMS AND CONDITIONS OF THIS AGREEMENT AND/OR THE TERMS AND + CONDITIONS OF LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW. BY USING THE CONTENT, YOU AGREE THAT YOUR USE + OF THE CONTENT IS GOVERNED BY THIS AGREEMENT AND/OR THE TERMS AND CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR + NOTICES INDICATED OR REFERENCED BELOW. IF YOU DO NOT AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT AND THE TERMS AND + CONDITIONS OF ANY APPLICABLE LICENSE AGREEMENTS OR NOTICES INDICATED OR REFERENCED BELOW, THEN YOU MAY NOT USE THE CONTENT.</p> + +<h3>Applicable Licenses</h3> + +<p>Unless otherwise indicated, all Content made available by the Eclipse Foundation is provided to you under the terms and conditions of the Eclipse Public License Version 1.0 + ("EPL"). A copy of the EPL is provided with this Content and is also available at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>. + For purposes of the EPL, "Program" will mean the Content.</p> + +<p>Content includes, but is not limited to, source code, object code, documentation and other files maintained in the Eclipse.org CVS repository ("Repository") in CVS + modules ("Modules") and made available as downloadable archives ("Downloads").</p> + +<p>Content may be apportioned into plug-ins ("Plug-ins"), plug-in fragments ("Fragments"), and features ("Features"). A Feature is a bundle of one or more Plug-ins and/or Fragments and associated material. Files named "feature.xml" may contain a list of the names and version numbers of the Plug-ins and/or Fragments associated with a Feature. Plug-ins and Fragments are located in directories + named "plugins" and Features are located in directories named "features".</p> + +<p>Features may also include other Features ("Included Features"). Files named "feature.xml" may contain a list of the names and version numbers of Included Features.</p> + +<p>The terms and conditions governing Plug-ins and Fragments should be contained in files named "about.html" ("Abouts"). The terms and conditions governing Features and +Included Features should be contained in files named "license.html" ("Feature Licenses"). Abouts and Feature Licenses may be located in any directory of a Download or Module +including, but not limited to the following locations:</p> + +<ul> + <li>The top-level (root) directory</li> + <li>Plug-in and Fragment directories</li> + <li>Subdirectories of the directory named "src" of certain Plug-ins</li> + <li>Feature directories</li> +</ul> + +<p>Note: if a Feature made available by the Eclipse Foundation is installed using the Eclipse Update Manager, you must agree to a license ("Feature Update License") during the +installation process. If the Feature contains Included Features, the Feature Update License should either provide you with the terms and conditions governing the Included Features or +inform you where you can locate them. Feature Update Licenses may be found in the "license" property of files named "feature.properties". +Such Abouts, Feature Licenses and Feature Update Licenses contain the terms and conditions (or references to such terms and conditions) that govern your use of the associated Content in +that directory.</p> + +<p>THE ABOUTS, FEATURE LICENSES AND FEATURE UPDATE LICENSES MAY REFER TO THE EPL OR OTHER LICENSE AGREEMENTS, NOTICES OR TERMS AND CONDITIONS. SOME OF THESE +OTHER LICENSE AGREEMENTS MAY INCLUDE (BUT ARE NOT LIMITED TO):</p> + +<ul> + <li>Common Public License Version 1.0 (available at <a href="http://www.eclipse.org/legal/cpl-v10.html">http://www.eclipse.org/legal/cpl-v10.html</a>)</li> + <li>Apache Software License 1.1 (available at <a href="http://www.apache.org/licenses/LICENSE">http://www.apache.org/licenses/LICENSE</a>)</li> + <li>Apache Software License 2.0 (available at <a href="http://www.apache.org/licenses/LICENSE-2.0">http://www.apache.org/licenses/LICENSE-2.0</a>)</li> + <li>IBM Public License 1.0 (available at <a href="http://oss.software.ibm.com/developerworks/opensource/license10.html">http://oss.software.ibm.com/developerworks/opensource/license10.html</a>)</li> + <li>Metro Link Public License 1.00 (available at <a href="http://www.opengroup.org/openmotif/supporters/metrolink/license.html">http://www.opengroup.org/openmotif/supporters/metrolink/license.html</a>)</li> + <li>Mozilla Public License Version 1.1 (available at <a href="http://www.mozilla.org/MPL/MPL-1.1.html">http://www.mozilla.org/MPL/MPL-1.1.html</a>)</li> +</ul> + +<p>IT IS YOUR OBLIGATION TO READ AND ACCEPT ALL SUCH TERMS AND CONDITIONS PRIOR TO USE OF THE CONTENT. If no About, Feature License or Feature Update License is provided, please +contact the Eclipse Foundation to determine what terms and conditions govern that particular Content.</p> + +<h3>Cryptography</h3> + +<p>Content may contain encryption software. The country in which you are currently may have restrictions on the import, possession, and use, and/or re-export to + another country, of encryption software. BEFORE using any encryption software, please check the country's laws, regulations and policies concerning the import, + possession, or use, and re-export of encryption software, to see if this is permitted.</p> +</body> +</html> diff --git a/org.eclipse.mylyn.bugzilla.core/.classpath b/org.eclipse.mylyn.bugzilla.core/.classpath new file mode 100644 index 000000000..a68b3ff0f --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/.classpath @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"> + <accessrules> + <accessrule kind="accessible" pattern="**/internal/**"/> + <accessrule kind="nonaccessible" pattern="**/System"/> + </accessrules> + </classpathentry> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> + <classpathentry kind="src" path="src"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/org.eclipse.mylyn.bugzilla.core/.cvsignore b/org.eclipse.mylyn.bugzilla.core/.cvsignore new file mode 100644 index 000000000..bc8b2d4cb --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/.cvsignore @@ -0,0 +1,3 @@ +bin +bugzilla.jar +doc.zip diff --git a/org.eclipse.mylyn.bugzilla.core/.project b/org.eclipse.mylyn.bugzilla.core/.project new file mode 100644 index 000000000..d9ab28171 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/.project @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eclipse.mylar.bugzilla</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.ManifestBuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.pde.SchemaBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.pde.PluginNature</nature> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/org.eclipse.mylyn.bugzilla.core/Icons/bug.gif b/org.eclipse.mylyn.bugzilla.core/Icons/bug.gif Binary files differnew file mode 100644 index 000000000..7f4b2a8d8 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/Icons/bug.gif diff --git a/org.eclipse.mylyn.bugzilla.core/Icons/bugzilla-bookmark.gif b/org.eclipse.mylyn.bugzilla.core/Icons/bugzilla-bookmark.gif Binary files differnew file mode 100644 index 000000000..89ca9d889 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/Icons/bugzilla-bookmark.gif diff --git a/org.eclipse.mylyn.bugzilla.core/Icons/openresult.gif b/org.eclipse.mylyn.bugzilla.core/Icons/openresult.gif Binary files differnew file mode 100644 index 000000000..2b645a88f --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/Icons/openresult.gif diff --git a/org.eclipse.mylyn.bugzilla.core/Icons/remove-all.gif b/org.eclipse.mylyn.bugzilla.core/Icons/remove-all.gif Binary files differnew file mode 100644 index 000000000..2c069ab3f --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/Icons/remove-all.gif diff --git a/org.eclipse.mylyn.bugzilla.core/Icons/remove.gif b/org.eclipse.mylyn.bugzilla.core/Icons/remove.gif Binary files differnew file mode 100644 index 000000000..12a9167c5 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/Icons/remove.gif diff --git a/org.eclipse.mylyn.bugzilla.core/Icons/selectAll.gif b/org.eclipse.mylyn.bugzilla.core/Icons/selectAll.gif Binary files differnew file mode 100644 index 000000000..e159862e3 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/Icons/selectAll.gif diff --git a/org.eclipse.mylyn.bugzilla.core/META-INF/MANIFEST.MF b/org.eclipse.mylyn.bugzilla.core/META-INF/MANIFEST.MF new file mode 100644 index 000000000..7d2e25e41 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/META-INF/MANIFEST.MF @@ -0,0 +1,36 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Bugzilla Plug-in +Bundle-SymbolicName: org.eclipse.mylar.bugzilla; singleton:=true +Bundle-Version: 1.7.2 +Bundle-Activator: org.eclipse.mylar.bugzilla.BugzillaPlugin +Bundle-Localization: plugin +Require-Bundle: org.eclipse.ui, + org.eclipse.core.runtime, + org.eclipse.core.resources, + org.eclipse.compare, + org.eclipse.search, + org.eclipse.ui.views, + org.eclipse.ui.ide, + org.eclipse.help, + org.eclipse.help.ui, + org.eclipse.jface.text, + org.eclipse.ui.workbench.texteditor, + org.eclipse.ui.editors, + org.eclipse.pde.ui +Eclipse-AutoStart: true +Bundle-Vendor: University of British Columbia +Bundle-ClassPath: bugzilla-eclipse.jar +Export-Package: org.eclipse.mylar.bugzilla, + org.eclipse.mylar.bugzilla.compare, + org.eclipse.mylar.bugzilla.core, + org.eclipse.mylar.bugzilla.core.internal, + org.eclipse.mylar.bugzilla.favorites, + org.eclipse.mylar.bugzilla.favorites.actions, + org.eclipse.mylar.bugzilla.offlineReports, + org.eclipse.mylar.bugzilla.saveQuery, + org.eclipse.mylar.bugzilla.search, + org.eclipse.mylar.bugzilla.ui, + org.eclipse.mylar.bugzilla.ui.editor, + org.eclipse.mylar.bugzilla.ui.outline, + org.eclipse.mylar.bugzilla.ui.wizard diff --git a/org.eclipse.mylyn.bugzilla.core/about.html b/org.eclipse.mylyn.bugzilla.core/about.html new file mode 100644 index 000000000..60ca57b4b --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/about.html @@ -0,0 +1,22 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3c.org/TR/1999/REC-html401-19991224/loose.dtd"> +<!-- saved from url=(0043)http://www.eclipse.org/legal/epl/about.html --> +<HTML><HEAD><TITLE>About</TITLE> +<META http-equiv=Content-Type content="text/html; charset=ISO-8859-1"> +<META content="MSHTML 6.00.2900.2627" name=GENERATOR></HEAD> +<BODY lang=EN-US> +<H2>About This Content</H2> +<P>February 24, 2005</P> +<H3>License</H3> +<P>The Eclipse Foundation makes available all content in this plug-in +("Content"). Unless otherwise indicated below, the Content is provided to you +under the terms and conditions of the Eclipse Public License Version 1.0 +("EPL"). A copy of the EPL is available at <A +href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</A>. +For purposes of the EPL, "Program" will mean the Content.</P> +<P>If you did not receive this Content directly from the Eclipse Foundation, the +Content is being redistributed by another party ("Redistributor") and different +terms and conditions may apply to your use of any object code in the Content. +Check the Redistributor's license that was provided with the Content. If no such +license exists, contact the Redistributor. Unless otherwise indicated below, the +terms and conditions of the EPL still apply to any source code in the +Content.</P></BODY></HTML> diff --git a/org.eclipse.mylyn.bugzilla.core/bugzilla_contexts.xml b/org.eclipse.mylyn.bugzilla.core/bugzilla_contexts.xml new file mode 100644 index 000000000..78b55ca14 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/bugzilla_contexts.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<contexts> + + <context id="bugzillaSearchContext"> + <description>Enter a Bug ID or some text to search the Bugzilla database for related bugs.</description> + <topic href="docs/html/start/bugzillaSearch.html" label="Bugzilla Search"/> + <topic href="http://www.bugzilla.org" label="Bugzilla Website"/> + </context> + + <context id="bugzillaEditorContext"> + <description>View the bug with the "Preview" tab, and submit changes with the "Submit" tab.</description> + <topic href="docs/html/start/submitEditor.html" label="Bugzilla Editor"/> + </context> + +</contexts>
\ No newline at end of file diff --git a/org.eclipse.mylyn.bugzilla.core/build.properties b/org.eclipse.mylyn.bugzilla.core/build.properties new file mode 100644 index 000000000..634644e41 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/build.properties @@ -0,0 +1,31 @@ +############################################################################### +# Copyright (c) 2003 - 2005 University Of British Columbia and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# University Of British Columbia - initial API and implementation +############################################################################### +bin.includes = plugin.xml,\ + toc.xml,\ + Icons/,\ + doc.zip,\ + bugzilla-eclipse.jar,\ + xercesImpl.jar,\ + META-INF/,\ + docs/,\ + bugzilla_contexts.xml,\ + about.html +source.bugzilla-eclipse.jar = src/ +output.bugzilla-eclipse.jar = bin/ +src.includes = META-INF/,\ + Icons/,\ + docs/,\ + plugin.xml,\ + src/,\ + toc.xml,\ + xercesImpl.jar,\ + bugzilla_contexts.xml,\ + about.html diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/Bugzilla.gif b/org.eclipse.mylyn.bugzilla.core/docs/html/Bugzilla.gif Binary files differnew file mode 100644 index 000000000..4d0c53d05 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/Bugzilla.gif diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/book.html b/org.eclipse.mylyn.bugzilla.core/docs/html/book.html new file mode 100644 index 000000000..10ffb6918 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/book.html @@ -0,0 +1,29 @@ +<!DOCTYPE doctype PUBLIC "-//w3c//dtd html 4.0 transitional//en"> + +<html> + +<head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <meta name="GENERATOR" content="Mozilla/4.73 [en] (Win98; U) [Netscape]"> + <title>Bugzilla: Bugzilla Database Server Querying</title> +</head> + +<body bgcolor="#c8ccad"> + <font face="arial, lucida console" size="+3"> + <table cellpadding="5"> + <tr> + <td><img src="Bugzilla.gif" alt="Bugzilla"></td> + <td valign=bottom><u><b>Bugzilla Database Server Querying</b></u><br><br></td> + </tr> + </table> + </font> + + <br> + <br> + <br> + <br> + <br> + +</body> + +</html> diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start.html b/org.eclipse.mylyn.bugzilla.core/docs/html/start.html new file mode 100644 index 000000000..e59973752 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start.html @@ -0,0 +1,17 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> + +<html> + +<head> + <title>Getting Started</title> +</head> + +<body> + <font face="arial, lucida console"> + <h3>Getting Started</h3> + <br> + <p>Setting up and configuring Bugzilla, as well as instructions on its use. + </font> +</body> + +</html> diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/bugEditor.html b/org.eclipse.mylyn.bugzilla.core/docs/html/start/bugEditor.html new file mode 100644 index 000000000..d18582899 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/bugEditor.html @@ -0,0 +1,56 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> + +<html> + +<head> + <title>Getting Started</title> +</head> + +<body> + <font face="arial, lucida console"> + + <h3>Bugzilla Editor</h3> + <br> + + <p><b>NOTE:</b> It is required that you have entered your Bugzilla login name and password + into the Bugzilla plugin preferences if you wish to create new bugs. + </p> + + <p>The bug editor is opened when you view a bug. Here, you can edit the bug and submit the changes. + Once you are finished modifying the bug, you can click the "Submit" button at the + bottom of the editor to submit it to the Bugzilla server. Also, if you have made + changes to the bug, you can click the "Compare" button to inspect any changes between + your version of the bug and the online version. + </p> + + <p>If you click the "Save" button, you will save the current version of the bug offline on your + hard-drive. The locally saved bug report will be accessible from the Offline Reports View. Any + time you try to view a bug with this id, your offline copy will be shown instead of the one + from the server. You can continue to make and save changes until you either delete the offline + version or submit your changes to the server. + </p> + + + <img src="./images/existing-bug-editor.png"><br> + + <p>A bug saved from the New Bug Wizard will open in a simpler editor, since it does + not yet exist on a server, and so there are fewer tasks you can perform on it. + The most notable change is that there is no button to allow you to compare it with + the online version. + </p> + + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + + </font> + +</body> +</html>
\ No newline at end of file diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/bugWizard.html b/org.eclipse.mylyn.bugzilla.core/docs/html/start/bugWizard.html new file mode 100644 index 000000000..df9abf109 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/bugWizard.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> + +<html> + +<head> + <title>Getting Started</title> +</head> + +<body> + <font face="arial, lucida console"> + + <h3>Bugzilla New Bug Wizard</h3> + <br> + + <p><b>NOTE:</b> It is required that you have entered your Bugzilla login name and password + into the Bugzilla plugin preferences if you wish to create new bugs. + </p> + + <p>This Bugzilla plugin allows you to submit new bugs. To do this, go to "File->New->Other..." + and then choose "Bugzilla Wizards" and "New Bug Report". + </p> + + <img src="./images/new-bug-wizard.png"><br> + + <p>When you click "Next", you will be presented with a dialog that contains the list + of products that you can submit a bug for. Choose the product that you wish to submit + the bug for and click "Next". If there is only one product to choose from, then this + screen will be skipped over automatically. + </p> + + <img src="./images/new-bug-wizard-products.png"><br> + + <p>Now, you can modify the required attributes for the new bug. When you are finished, + you can submit the bug to the server. To do so, select "Submit bug report to the server." + and click the "Finish" button. This will submit your new bug to the server. If no + problems occured, the new bug will be opened in a Bugzilla editor so that you can review + your submission. + </p> + + <img src="./images/new-bug-wizard-attributes.png"><br> + + <p>Alternatively, you can save the bug offline on your hard-drive. Select "Save bug + report offline." and click the "Finish" button. The locally saved bug report will be + accessible from the Offline Reports View.</p> + + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + + </font> + +</body> +</html>
\ No newline at end of file diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/bugzillaFavorites.html b/org.eclipse.mylyn.bugzilla.core/docs/html/start/bugzillaFavorites.html new file mode 100644 index 000000000..d5ab217e7 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/bugzillaFavorites.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> + +<head> + <title>Getting Started</title> +</head> + +<body> + <font face="arial, lucida console"> + + <h3>Bugzilla Favorites</h3> + <br> + + <p> + Bugzilla has the ability to save bugs that are frequently accessed as a favorite + similar to a web browser. To do this, after a search is done right click on the bug + in the Search results window that you would like to save and choose "Mark Result as Favorite". + </p> + + <img src="./images/bugzilla-search-context.png"><br> + + <p> + A new window will now open that contains your specific list of favorites. To access + any of the bugs in the favorites view, just double click on the one that you want to + open and a Bugzilla bug editor will open containing the specific bug. To access the favorites + menu without adding a new bug, go to Window --> Show View --> Other... --> Bugzilla and click + on Bugzilla Favorites. + </p> + + <img src="./images/bugzilla-favorites-window.png"><br> + + + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + + </font> +</body> + +</html> diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/bugzillaSearch.html b/org.eclipse.mylyn.bugzilla.core/docs/html/start/bugzillaSearch.html new file mode 100644 index 000000000..412906e5b --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/bugzillaSearch.html @@ -0,0 +1,66 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> + +<head> + <title>Getting Started</title> +</head> + +<body> + <font face="arial, lucida console"> + + <h3>Bugzilla Search</h3> + <br> + + <p> + A Bugzilla Search tab has also been added to the Eclipse Search pane. You can enter + a bug ID or keywords in the text field and, if desired, limit the + search by selecting particular attributes that the bug must have. + Press Enter or click "Search" to proceed with the query to the Bugzilla server. + </p> + + <img src="./images/bugzilla-search-dialog.png"><br> + + <p> + If a Bugzilla ID was entered, that particular bug report will open in + the Bugzilla bug viewer. + </p> + + <img src="./images/bugzilla-bug-editor.png"><br> + + <p> + If a bug id was not entered, the results will appear in the Search results tab. + From here, you can open any of the items by double-clicking on them. + </p> + + <img src="./images/bugzilla-searchresults.png"> + + <p> + By default, all of the results are sorted by Bug ID. They can also be sorted by + priority, severity, or status. To change the way the items are sorted, right-click + any bug and choose from the "Sort By" submenu. + </p> + + <img src="./images/bugzilla-search-sorting.png"> + + <p> + Previous Bugzilla queries are accessible through the drop-down + history menu at the right end of the view's toolbar. + </p> + + <img src="./images/previous-searches.png"><br> + + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + + </font> +</body> + +</html> diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/bugzillaUpdate.html b/org.eclipse.mylyn.bugzilla.core/docs/html/start/bugzillaUpdate.html new file mode 100644 index 000000000..5b0dc1341 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/bugzillaUpdate.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> + +<head> + <title>Getting Started</title> +</head> + +<body> + <font face="arial, lucida console"> + + <h3>Bugzilla Update</h3> + <br> + + <p> + There is an update button near the bottom of the Bugzilla search dialog. + When this button is clicked, it will query the Bugzilla server for new + options. These options are for the bug attributes such as milestone, product, etc. + This allows Bugzilla to contain the latest information so that you can + perform a better search. + </p> + + <img src="./images/bugzilla-search-dialog.png"><br> + + <p> + When the update button is clicked, a status bar will appear at the bottom + of the search dialog so that you can see the progress of the update. Once the operation + is complete, the bug attributes will be updated to contain their latest values. + </p> + + <img src="./images/update-search-dialog.png"><br> + + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + + </font> +</body> + +</html> diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/Icons/remove-all.gif b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/Icons/remove-all.gif Binary files differnew file mode 100644 index 000000000..2c069ab3f --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/Icons/remove-all.gif diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/Icons/remove.gif b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/Icons/remove.gif Binary files differnew file mode 100644 index 000000000..12a9167c5 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/Icons/remove.gif diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-bug-editor.png b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-bug-editor.png Binary files differnew file mode 100644 index 000000000..192110478 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-bug-editor.png diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-favorites-window.png b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-favorites-window.png Binary files differnew file mode 100644 index 000000000..01f46ae83 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-favorites-window.png diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-search-context.png b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-search-context.png Binary files differnew file mode 100644 index 000000000..4c883b54d --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-search-context.png diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-search-dialog.png b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-search-dialog.png Binary files differnew file mode 100644 index 000000000..93098c8ea --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-search-dialog.png diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-search-sorting.png b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-search-sorting.png Binary files differnew file mode 100644 index 000000000..9ceb8f0e1 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-search-sorting.png diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-searchresults.png b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-searchresults.png Binary files differnew file mode 100644 index 000000000..5f538de0c --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-searchresults.png diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/existing-bug-editor.png b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/existing-bug-editor.png Binary files differnew file mode 100644 index 000000000..82ced89e4 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/existing-bug-editor.png diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/new-bug-wizard-attributes.png b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/new-bug-wizard-attributes.png Binary files differnew file mode 100644 index 000000000..027fea913 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/new-bug-wizard-attributes.png diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/new-bug-wizard-products.png b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/new-bug-wizard-products.png Binary files differnew file mode 100644 index 000000000..859533dba --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/new-bug-wizard-products.png diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/new-bug-wizard.png b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/new-bug-wizard.png Binary files differnew file mode 100644 index 000000000..d0e05339a --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/new-bug-wizard.png diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/offline-reports.png b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/offline-reports.png Binary files differnew file mode 100644 index 000000000..966384dcf --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/offline-reports.png diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/prefs.png b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/prefs.png Binary files differnew file mode 100644 index 000000000..c608502d1 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/prefs.png diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/previous-searches.png b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/previous-searches.png Binary files differnew file mode 100644 index 000000000..8bcb49d1b --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/previous-searches.png diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/remember-query-overwrite.png b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/remember-query-overwrite.png Binary files differnew file mode 100644 index 000000000..e16bbb8ca --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/remember-query-overwrite.png diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/remember-query.png b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/remember-query.png Binary files differnew file mode 100644 index 000000000..4d8ac7e1a --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/remember-query.png diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/saved-query-list.png b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/saved-query-list.png Binary files differnew file mode 100644 index 000000000..c7681ee9a --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/saved-query-list.png diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/update-search-dialog.png b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/update-search-dialog.png Binary files differnew file mode 100644 index 000000000..c28f642c6 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/update-search-dialog.png diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/offlineReports.html b/org.eclipse.mylyn.bugzilla.core/docs/html/start/offlineReports.html new file mode 100644 index 000000000..8c4f10fc5 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/offlineReports.html @@ -0,0 +1,37 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> + +<html> + +<head> + <title>Getting Started</title> +</head> + +<body> + <font face="arial, lucida console"> + + <h3>Offline Reports</h3> + <br> + + <p>Bugzilla has the ability to save bugs offline on your hard-drive from both the bug editor and the + New Bug Wizard. The Offline Reports View is where the saved bug reports can be accessed. To show the + offline reports menu without saving a new bug, go to Window --> Show View --> Other... --> Bugzilla + and click on Bugzilla Offline Reports. To open an offline bug, simply double-click it. + </p> + + <img src="./images/offline-reports.png"><br> + + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + + </font> + +</body> +</html>
\ No newline at end of file diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/saveQuery.html b/org.eclipse.mylyn.bugzilla.core/docs/html/start/saveQuery.html new file mode 100644 index 000000000..d80fa6f6d --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/saveQuery.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> + +<head> + <title>Getting Started</title> +</head> + +<body> + <font face="arial, lucida console"> + + <h3>Save Query</h3> + <br> + + <p> + There is a pair of buttons in the middle of the search dialog that allow you + to save frequently used queries and run these saved queries. The "Remember..." button + will save the current query, and the "Saved Queries..." button will display + a list of saved queries that you can choose from. + </p> + + <img src="./images/bugzilla-search-dialog.png"><br> + + <p> + When the "Remember..." button is clicked, a dialog will be displayed asking + you to name the query. Once "OK" is clicked, the query will be saved locally. + </p> + + <img src="./images/remember-query.png"><br> + + <p> + If you try to name a new query with an name that already exists, you will be prompted + asking whether to overwrite the currently saved query with the new one. + </p> + + <img src="./images/remember-query-overwrite.png"><br> + + <p> + When the "Saved Queries..." button is clicked on, another dialog will be displayed + containing a list of all of the queries that are currently saved on your computer. Select + the query that you wish to run and click "run" to execute it. + </p> + + <img src="./images/saved-query-list.png"><br> + + <br> + + </font> +</body> + +</html> diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/setup.html b/org.eclipse.mylyn.bugzilla.core/docs/html/start/setup.html new file mode 100644 index 000000000..9f7c5a83f --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/setup.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> + +<html> + +<head> + <title>Getting Started</title> +</head> + +<body> + <font face="arial, lucida console"> + + <h3>Bugzilla Setup</h3> + <br> + + <p>Although default settings are provided, the bugzilla server can be changed. To change the configuration, go to Preferences (in the Window menu) and open the Bugzilla tab. + </p> + + <p>Here, the following parameters can be set: + + <ol> + <li>URL of the Bugzilla server (set by default to the Eclipse Bugzilla server). This is used by the bug report + viewer to view Bugzilla items within Eclipse.</li> + + <li>Your login name for the Bugzilla server (optional).</li> + <li>Your password for the Bugzilla server (optional).</li> + + </ol> + <br> + Your Bugzilla login name and password are only required if you wish to create or modify bugs + using this Bugzilla plugin. Viewing bugs using this plugin does not require that a user name + and password be specified. + </p> + + <img src="./images/prefs.png"> + <br> + <br> + + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + <br> + + </font> + +</body> +</html>
\ No newline at end of file diff --git a/org.eclipse.mylyn.bugzilla.core/docs/html/start/use.html b/org.eclipse.mylyn.bugzilla.core/docs/html/start/use.html new file mode 100644 index 000000000..e1c0599f4 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/use.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> + +<head> + <title>Getting Started</title> +</head> + +<body> + <font face="arial, lucida console"> + + <h3>Using Bugzilla</h3> + <br> + + <p>How to use the various functions of bugzilla, and interpret their results. + + </font> +</body> + +</html> diff --git a/org.eclipse.mylyn.bugzilla.core/plugin.xml b/org.eclipse.mylyn.bugzilla.core/plugin.xml new file mode 100644 index 000000000..ce5c0aae7 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/plugin.xml @@ -0,0 +1,131 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?eclipse version="3.0"?> +<?eclipse version="3.0"?> +<plugin> + + <extension + id="org.eclipse.mylar.bugzilla.wizards" + name="Bug Wizard" + point="org.eclipse.ui.newWizards"> + <category + name="Bugzilla Wizards" + id="org.eclipse.mylar.bugzilla.wizard.category"> + </category> + <wizard + name="New Bug Report" + icon="Icons/bug.gif" + category="org.eclipse.mylar.bugzilla.wizard.category" + class="org.eclipse.mylar.bugzilla.ui.wizard.NewBugWizard" + id="org.eclipse.mylar.bugzilla.bugWizard"> + <description> + Create a new bug report + </description> + </wizard> +</extension> + + <extension + id="org.eclipse.mylar.bugzilla.help.browser" + name="Bugzilla Help" + point="org.eclipse.help.toc"> + <toc + file="toc.xml" + primary="true"> + </toc> + </extension> + <extension + id="org.eclipse.mylar.bugzilla.favorites.view" + name="Bugzilla Favorites View" + point="org.eclipse.ui.views"> + <category + name="Bugzilla" + id="org.eclipse.mylar.bugzilla.category"/> + <view + name="Bugzilla Favorites" + icon="Icons/bugzilla-bookmark.gif" + category="org.eclipse.mylar.bugzilla.category" + class="org.eclipse.mylar.bugzilla.ui.FavoritesView" + id="org.eclipse.mylar.bugzilla.ui.favoritesView"/> + </extension> + <extension + id="org.eclipse.mylar.bugzilla.favorites.view" + name="Bugzilla Offline Reports View" + point="org.eclipse.ui.views"> + <category + name="Bugzilla" + id="org.eclipse.mylar.bugzilla.category"/> + <view + name="Bugzilla Offline Reports" + icon="Icons/bugzilla-bookmark.gif" + category="org.eclipse.mylar.bugzilla.category" + class="org.eclipse.mylar.bugzilla.ui.OfflineView" + id="org.eclipse.mylar.bugzilla.ui.offlineReportsView"/> + </extension> + <extension + point="org.eclipse.search.searchPages" + id="org.eclipse.mylar.bugzilla.search.searchPage" + name="Bugzilla Search Page"> + <page + label="Bugzilla Search" + enabled="true" + icon="Icons/bug.gif" + class="org.eclipse.mylar.bugzilla.search.BugzillaSearchPage" + id="org.eclipse.mylar.bugzilla.search.bugzillaSearchPage"/> + </extension> + <extension + point="org.eclipse.ui.editors"> + <editor + name="Bugzilla viewer" + icon="Icons/bug.gif" + class="org.eclipse.mylar.bugzilla.ui.editor.ExistingBugEditor" + id="org.eclipse.mylar.bugzilla.ui.existingBugEditor"> + </editor> + <editor + name="Bugzilla viewer" + icon="Icons/bug.gif" + class="org.eclipse.mylar.bugzilla.ui.editor.NewBugEditor" + id="org.eclipse.mylar.bugzilla.ui.newBugEditor"> + </editor> + </extension> + <extension + id="searchHit" + name="Bugzilla Search Match" + point="org.eclipse.core.resources.markers"> + <super type="org.eclipse.search.searchmarker"/> + <attribute name="id"/> + <attribute name="href"/> + <attribute name="description"/> + <attribute name="label"/> + <attribute name="severity"/> + <attribute name="priority"/> + <attribute name="platform"/> + <attribute name="status"/> + <attribute name="result"/> + <attribute name="owner"/> + <attribute name="query"/> + </extension> + <extension + point="org.eclipse.ui.preferencePages"> + <page + name="Bugzilla" + class="org.eclipse.mylar.bugzilla.BugzillaPreferences" + id="org.eclipse.mylar.bugzilla.bugzillaPreferences"/> + </extension> + <extension + id="org.eclipse.mylar.bugzilla.help.context" + name="Bugzilla Context-sensitive Help" + point="org.eclipse.help.contexts"> + <contexts + file="bugzilla_contexts.xml"> + </contexts> + </extension> + <extension + id="BugzillaSearchPage" + point="org.eclipse.search.searchResultViewPages"> + <viewPage + class="org.eclipse.mylar.bugzilla.search.BugzillaSearchResultView" + searchResultClass="org.eclipse.mylar.bugzilla.search.BugzillaSearchResult" + id="org.eclipse.mylar.bugzilla.BugzillaSearchResultPage"/> + </extension> + +</plugin> + diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/BugzillaImages.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/BugzillaImages.java new file mode 100644 index 000000000..7af463b56 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/BugzillaImages.java @@ -0,0 +1,142 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.resource.ImageRegistry; +import org.eclipse.swt.graphics.Image; + +/** + * This class provides convenience access to many of the resources required + * by the workbench. The class stores some images as descriptors, and + * some are stored as real Images in the registry. This is a pure + * speed-space tradeoff. The trick for users of this class is that + * images obtained from the registry (using getImage()), don't require + * disposal since they are shared, while images obtained using + * getImageDescriptor() will require disposal. Consult the declareImages + * method to see if a given image is declared as a registry image or + * just as a descriptor. If you change an image from being stored + * as a descriptor to a registry image, or vice-versa, make sure to + * check all users of the image to ensure they are calling + * the correct getImage... method and handling disposal correctly. + * + * Images: + * - use getImage(key) to access cached images from the registry. + * - Less common images are found by calling getImageDescriptor(key) + * where key can be found in IWorkbenchGraphicConstants + * + * This class initializes the image registry by declaring all of the required + * graphics. This involves creating image descriptors describing + * how to create/find the image should it be needed. + * The image is not actually allocated until requested. + * + * Some Images are also made available to other plugins by being + * placed in the descriptor table of the SharedImages class. + * + * Where are the images? + * The images (typically gifs) are found the plugins install directory + * + * How to add a new image + * Place the gif file into the appropriate directories. + * Add a constant to IWorkbenchGraphicConstants following the conventions + * Add the declaration to this file + */ +public class BugzillaImages { + public static final String IMG_TOOL_ADD_TO_FAVORITES = "IMG_TOOL_FAVORITE"; + + public static final String BUG = "IMG_BUG"; + + + private static HashMap<String, ImageDescriptor> descriptors = new HashMap<String, ImageDescriptor>(); + + private static ImageRegistry imageRegistry; + + // Subdirectory (under the package containing this class) where 16 color images are + private static final URL URL_BASIC = BugzillaPlugin.getDefault().getBundle().getEntry("/"); + + public final static String ICONS_PATH = "Icons/";//$NON-NLS-1$ + + private final static void declareImages() { + // toolbar icons for the result view + declareImage(IMG_TOOL_ADD_TO_FAVORITES, ICONS_PATH+"bugzilla-bookmark.gif");//$NON-NLS-1$ + declareImage(BUG, ICONS_PATH+"bug.gif");//$NON-NLS-1$ + } + + /** + * Declare an ImageDescriptor in the descriptor table. + * @param key The key to use when registering the image + * @param path The path where the image can be found. This path is relative to where + * this plugin class is found (i.e. typically the packages directory) + */ + private final static void declareImage(String key,String path) { + URL url = null; + try { + url = new URL(URL_BASIC, path); + } catch (MalformedURLException e) { + BugzillaPlugin.log(new Status(IStatus.WARNING, IBugzillaConstants.PLUGIN_ID,IStatus.OK,"Unable to declare the image for: " + path, e)); + } + ImageDescriptor desc = ImageDescriptor.createFromURL(url); + descriptors.put(key, desc); + } + + /** + * Returns the image stored in the workbench plugin's image registry + * under the given symbolic name. If there isn't any value associated + * with the name then <code>null</code> is returned. + * + * The returned Image is managed by the workbench plugin's image registry. + * Callers of this method must not dispose the returned image. + * + * This method is essentially a convenient short form of + * HipikatImages.getImageRegistry.get(symbolicName). + */ + public static Image getImage(String symbolicName) { + return getImageRegistry().get(symbolicName); + } + + /** + * Returns the image descriptor stored under the given symbolic name. + * If there isn't any value associated with the name then <code>null + * </code> is returned. + * + * The class also "caches" commonly used images in the image registry. + * If you are looking for one of these common images it is recommended you use + * the getImage() method instead. + */ + public static ImageDescriptor getImageDescriptor(String symbolicName) { + if (imageRegistry == null) { + initializeImageRegistry(); + } + return descriptors.get(symbolicName); + } + + /** + * Returns the ImageRegistry. + */ + public static ImageRegistry getImageRegistry() { + if (imageRegistry == null) { + initializeImageRegistry(); + } + return imageRegistry; + } + + private static ImageRegistry initializeImageRegistry() { + imageRegistry = new ImageRegistry(); + declareImages(); + return imageRegistry; + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/BugzillaPlugin.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/BugzillaPlugin.java new file mode 100644 index 000000000..dcfeed58d --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/BugzillaPlugin.java @@ -0,0 +1,288 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla; + +import java.io.IOException; +import java.util.List; + +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.MultiStatus; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.mylar.bugzilla.core.IBugzillaBug; +import org.eclipse.mylar.bugzilla.core.internal.ProductConfiguration; +import org.eclipse.mylar.bugzilla.core.internal.ProductConfigurationFactory; +import org.eclipse.mylar.bugzilla.favorites.FavoritesFile; +import org.eclipse.mylar.bugzilla.offlineReports.OfflineReportsFile; +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.osgi.framework.BundleContext; + + +/** + * @author kvesik + * + * Created on Mar 26, 2003 + */ + +/** + * The main plugin class to be used in the desktop. + */ +public class BugzillaPlugin extends AbstractUIPlugin { + + /** Singleton instance of the plug-in */ + private static BugzillaPlugin plugin; + + /** The file that contains all of the bugzilla favorites */ + private FavoritesFile favoritesFile; + + /** The file that contains all of the offline bug reports */ + private OfflineReportsFile offlineReportsFile; + + /** Product configuration for the current server */ + private ProductConfiguration productConfiguration; + + /** + * Constructor + * @param descriptor passed in when the plugin is loaded + */ + public BugzillaPlugin() + { + super(); + } + + /** + * Get the singleton instance for the plugin + * + * @return The instance of the plugin + */ + public static BugzillaPlugin getDefault() + { + return plugin; + } + + @Override + public void start(BundleContext context) throws Exception { + super.start(context); + plugin = this; + plugin = this; + readFavoritesFile(); + readOfflineReportsFile(); + readCachedProductConfiguration(); + } + + @Override + public void stop(BundleContext context) throws Exception { + plugin = null; + super.stop(context); + } + + + /** + * Get the favorites file contatining the favorites + * + * @return The FavoritesFile + */ + public FavoritesFile getFavorites() + { + return favoritesFile; + } + + /** + * Get the OfflineReports file contatining the offline bug reports + * + * @return The OfflineReportsFile + */ + public OfflineReportsFile getOfflineReports() + { + return offlineReportsFile; + } + + /** + * Get the name of the bugzilla server + * + * @return A string containing the prefered name of the bugzilla server + */ + public String getServerName() + { + return plugin.getPreferenceStore().getString(IBugzillaConstants.BUGZILLA_SERVER); + } + + /** + * Get the most recent query key for the preferences + * + * @return A string containing the most recent query key + */ + public static String getMostRecentQuery() + { + return plugin.getPreferenceStore().getString(IBugzillaConstants.MOST_RECENT_QUERY); + } + + /** + * Returns the current product configuration or <code>null</code> if it is unknown. + */ + public ProductConfiguration getProductConfiguration() { + return productConfiguration; + } + + /** + * Sets the current product configuration. + */ + protected void setProductConfiguration(ProductConfiguration productConfiguration) { + this.productConfiguration = productConfiguration; + } + + /** + * Reads cached product configuration and stores it in the <code>productConfiguration</code> field. + */ + private void readFavoritesFile() { + IPath favoritesPath = getFavoritesFile(); + + try { + favoritesFile = new FavoritesFile(favoritesPath.toFile()); + } catch (Exception e) { + logAndShowExceptionDetailsDialog(e, "occurred while restoring saved Bugzilla favorites.", "Bugzilla Favorites Error"); + } + } + + /** + * Reads cached product configuration and stores it in the <code>productConfiguration</code> field. + */ + private void readOfflineReportsFile() { + IPath offlineReportsPath = getOfflineReportsFile(); + + try { + offlineReportsFile = new OfflineReportsFile(offlineReportsPath.toFile()); + } catch (Exception e) { + logAndShowExceptionDetailsDialog(e, "occurred while restoring saved offline Bugzilla reports.", "Bugzilla Offline Reports Error"); + } + } + + /** + * Returns the path to the file cacheing the query favorites. + */ + private IPath getFavoritesFile() { + IPath stateLocation = Platform.getPluginStateLocation(BugzillaPlugin.getDefault()); + IPath configFile = stateLocation.append("favorites"); + return configFile; + } + + /** + * Returns the path to the file cacheing the offline bug reports. + */ + private IPath getOfflineReportsFile() { + IPath stateLocation = Platform.getPluginStateLocation(BugzillaPlugin.getDefault()); + IPath configFile = stateLocation.append("offlineReports"); + return configFile; + } + + /** + * Reads cached product configuration and stores it in the <code>productConfiguration</code> field. + */ + private void readCachedProductConfiguration() { + IPath configFile = getProductConfigurationCachePath(); + + try { + productConfiguration = ProductConfigurationFactory.getInstance().readConfiguration(configFile.toFile()); + } catch (IOException ex) { + try { + log(ex); + productConfiguration = ProductConfigurationFactory.getInstance().getConfiguration(getServerName()); + } catch (IOException e) { + log(e); + MessageDialog.openInformation(null, "Bugzilla product attributes check", + "An error occurred while restoring saved Bugzilla product attributes: \n\n" + + ex.getMessage() + + "\n\nUpdating them from the server also caused an error:\n\n" + + e.getMessage() + + "\n\nCheck the server URL in Bugzila preferences.\n" + + "Offline submission of new bugs will be disabled until valid product attributes have been loaded."); + } + } + } + + /** + * Returns the path to the file cacheing the product configuration. + */ + protected IPath getProductConfigurationCachePath() { + IPath stateLocation = Platform.getPluginStateLocation(BugzillaPlugin.getDefault()); + IPath configFile = stateLocation.append("productConfig"); + return configFile; + } + + @Override + protected void initializeDefaultPreferences(IPreferenceStore store) + { + BugzillaPreferences.initDefaults(store); + } + + /** + * Convenience method for logging statuses to the plugin log + * + * @param status the status to log + */ + public static void log(IStatus status) + { + getDefault().getLog().log(status); + } + + /** + * Convenience method for logging exceptions to the plugin log + * @param e the exception to log + */ + public static void log(Exception e) { + log(new Status(Status.ERROR, IBugzillaConstants.PLUGIN_ID, 0, e.getMessage(), e)); + } + + + /** + * Returns the path to the file caching bug reports created while offline. + */ + protected IPath getCachedBugReportPath(){ + IPath stateLocation = Platform.getPluginStateLocation(BugzillaPlugin.getDefault()); + IPath bugFile = stateLocation.append("bugReports"); + return bugFile; + } + + /** + * Logs the exception and shows an error dialog with exception details shown in a "Details" pane. + * @param e + * exception to be shown in the details pane + * @param message + * message to be used in the dialog + * @param title + * error dialog's title + */ + public IStatus logAndShowExceptionDetailsDialog(Exception e, String message, String title) { + MultiStatus status = new MultiStatus( IBugzillaConstants.PLUGIN_ID, + IStatus.ERROR, e.getClass().toString() + + " " + message + "\n\n" + + "Click Details or see log for more information.", e); + Status s = new Status(IStatus.ERROR, IBugzillaConstants.PLUGIN_ID, IStatus.ERROR, e.getClass().toString() + ": ", e); + status.add(s); + String error = (e.getMessage() == null)?e.getClass().toString():e.getMessage(); + s = new Status (IStatus.ERROR, IBugzillaConstants.PLUGIN_ID, IStatus.ERROR, error, e); + status.add(s); + log(status); + ErrorDialog.openError(null, title, null, status); + return status; + } + + /** + * @return a list of the BugReports saved offline. + */ + public List<IBugzillaBug> getSavedBugReports() { + return offlineReportsFile.elements(); + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/BugzillaPreferences.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/BugzillaPreferences.java new file mode 100644 index 000000000..928de7136 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/BugzillaPreferences.java @@ -0,0 +1,584 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.security.auth.login.LoginException; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.preference.BooleanFieldEditor; +import org.eclipse.jface.preference.FieldEditorPreferencePage; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.preference.StringFieldEditor; +import org.eclipse.mylar.bugzilla.core.BugzillaException; +import org.eclipse.mylar.bugzilla.core.internal.ProductConfiguration; +import org.eclipse.mylar.bugzilla.core.internal.ProductConfigurationFactory; +import org.eclipse.mylar.bugzilla.search.BugzillaQueryPageParser; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPreferencePage; + + +/** + * @version 1.0 + * @author G. Murphy + */ + +/** + * The class that deals with the preferences page in eclipse + */ +public class BugzillaPreferences + extends FieldEditorPreferencePage + implements IWorkbenchPreferencePage { + + /** Secure http server prefix */ + private static final String httpsPrefix = "https://"; + + /** http prefix */ + private static final String httpPrefix = "http://"; + + /** The text to put into the label for the bugzilla server text box */ + private static final String bugzillaServerLabel = "Bugzilla Server: "; + + /** Field editor for the bugzilla server in the preferences page */ + private StringFieldEditor bugzillaServer; + + private static final String bugzillaUserLabel = "Bugzilla User Name: "; + + private static final String bugzillaPasswordLabel = "Bugzilla Password: "; + + private static final String bugzilla218Label = "Using Bugzilla 2.18 "; + + private BooleanFieldEditor bugzilla218; + + private StringFieldEditor bugzillaUser; + + private MyStringFieldEditor bugzillaPassword; + + /** + * Constructor for the preferences page + */ + public BugzillaPreferences() { + super(GRID); + + // set the preference store for this preference page + setPreferenceStore(BugzillaPlugin.getDefault().getPreferenceStore()); + } + + @Override + public void createControl(Composite parent) { + super.createControl(parent); + } + + @Override + protected void createFieldEditors() { + // create a new field editor for the bugzilla server + bugzillaServer = new StringFieldEditor( + IBugzillaConstants.BUGZILLA_SERVER, bugzillaServerLabel, + StringFieldEditor.UNLIMITED, getFieldEditorParent()) { + + @Override + protected boolean doCheckState() { + return checkServerName(getStringValue()); + } + }; + + // set the error message for if the server name check fails + bugzillaServer.setErrorMessage( + "Server path must be a valid http(s):// url"); + + bugzillaUser = new StringFieldEditor("", bugzillaUserLabel, + StringFieldEditor.UNLIMITED, getFieldEditorParent()); + bugzillaPassword = new MyStringFieldEditor("", bugzillaPasswordLabel, + StringFieldEditor.UNLIMITED, getFieldEditorParent()); + bugzillaPassword.getTextControl().setEchoChar('*'); + + bugzilla218 = new BooleanFieldEditor(IBugzillaConstants.IS_218, bugzilla218Label, BooleanFieldEditor.DEFAULT, getFieldEditorParent()); + + // add the field editor to the preferences page + addField(bugzillaServer); + addField(bugzillaUser); + addField(bugzillaPassword); + addField(bugzilla218); + + // put the password and user name values into the field editors + getCachedData(); + bugzillaUser.setStringValue(user); + bugzillaPassword.setStringValue(password); + } + + /** + * Initialize the preferences page with the default values + * + * @param store The preferences store that is used to store the information about the preferences page + */ + public static void initDefaults(IPreferenceStore store) { + // set the default values for the bugzilla server and the + // most recent query + getCachedData(); + + store.setDefault(IBugzillaConstants.BUGZILLA_SERVER,IBugzillaConstants.DEFAULT_BUGZILLA_SERVER); + store.setDefault(IBugzillaConstants.MOST_RECENT_QUERY, ""); + store.setDefault(IBugzillaConstants.IS_218, true); + + // set the default query options for the bugzilla search + setDefaultQueryOptions(); + } + + @Override + protected void performDefaults() { + super.performDefaults(); + + /* + * set user and password to the new default values + * and then give these values to storeCache() to update the keyring + */ + user = bugzillaUser.getStringValue(); + password = bugzillaPassword.getStringValue(); + storeCache(user, password, true); + } + + @Override + public boolean performOk() { + BugzillaPlugin.getDefault().getPreferenceStore().setValue(IBugzillaConstants.IS_218, bugzilla218.getBooleanValue()); + String oldBugzillaServer = BugzillaPlugin.getDefault().getServerName(); + ProductConfiguration configuration = null; + + try { + SSLContext ctx = SSLContext.getInstance("TLS"); + + javax.net.ssl.TrustManager[] tm = new javax.net.ssl.TrustManager[]{new TrustAll()}; + ctx.init(null, tm, null); + HttpsURLConnection.setDefaultSSLSocketFactory(ctx.getSocketFactory()); + + // append "/show_bug.cgi" to url provided for cases where the connection is successful, + // but full path hasn't been specified (i.e. http://hipikat.cs.ubc.ca:8081) + URL serverURL = new URL(bugzillaServer.getStringValue() + "/show_bug.cgi"); + + HttpURLConnection serverConnection = (HttpURLConnection) serverURL.openConnection(); + + serverConnection.connect(); + + int responseCode = serverConnection.getResponseCode(); + + if(responseCode != HttpURLConnection.HTTP_OK) + throw new BugzillaException("No Bugzilla server detected at " + bugzillaServer.getStringValue() + "."); + + try { + configuration = ProductConfigurationFactory.getInstance().getConfiguration(bugzillaServer.getStringValue()); + } catch (IOException ex) { + MessageDialog.openInformation(null, "Bugzilla query parameters check", + "An error occurred while pre-fetching valid search attributes: \n\n" + + ex.getClass().getName() + ": " + ex.getMessage() + + "\n\nOffline submission of new bugs will be disabled."); + } + } + + catch (Exception e) { + if (!MessageDialog.openQuestion(null, "Bugzilla Server Error", "Error validating Bugzilla Server.\n\n" + + e.getMessage() + + "\n\nKeep specified server location anyway?")) + { + bugzillaServer.setStringValue(oldBugzillaServer); + return false; + } + } + + // save the preferences that were changed + //BugzillaPlugin.getDefault().savePluginPreferences(); + + bugzillaServer.store(); + + // store the username and password from the editor field + user = bugzillaUser.getStringValue(); + password = bugzillaPassword.getStringValue(); + storeCache(user, password, true); + + BugzillaPlugin.getDefault().setProductConfiguration(configuration); + IPath configFile = BugzillaPlugin.getDefault().getProductConfigurationCachePath(); + if (configuration != null) { + + try { + ProductConfigurationFactory.getInstance().writeConfiguration(configuration, configFile.toFile()); + } catch (IOException e) { + BugzillaPlugin.log(e); + configFile.toFile().delete(); + } + } + else { + configFile.toFile().delete(); + } + + return true; + } + + /** + * Determine if the name starts with https:// or http:// + * + * @param name The string that needs to be checked + * @return <code>true</code> if the name starts with https:// or http://, otherwise <code>false</code> + */ + private boolean checkServerName(String name) { + if (name.startsWith(httpsPrefix) || name.startsWith(httpPrefix)) + return true; + return false; + } + + @Override + protected void initialize() { + super.initialize(); + + // put the password and user name values into the field editors + getCachedData(); + bugzillaUser.setStringValue(user); + bugzillaPassword.setStringValue(password); + } + + public void init(IWorkbench workbench) { + // Don't need to do anything here with the workbench + } + + /** + * Hack private class to make StringFieldEditor.refreshValidState() + * a publicly acessible method. + * + * @see org.eclipse.jface.preference.StringFieldEditor#refreshValidState() + */ + private static class MyStringFieldEditor extends StringFieldEditor { + public MyStringFieldEditor(String name, String labelText, int style, + Composite parent) { + super(name, labelText, style, parent); + } + + @Override + public void refreshValidState() { + super.refreshValidState(); + } + + @Override + public Text getTextControl() { + return super.getTextControl(); + } + + } + + /** + * Update all of the query options for the bugzilla search page + * + * @param monitor A reference to a progress monitor + */ + public static void updateQueryOptions(IProgressMonitor monitor) + throws LoginException { + // make a new page parser so that we can get the information from the server + BugzillaQueryPageParser parser = new BugzillaQueryPageParser(monitor); + if (!parser.wasSuccessful()) + return; + + // get the preferences store so that we can change the data in it + IPreferenceStore prefs = BugzillaPlugin.getDefault().getPreferenceStore(); + + // get the new values for the status field and increment the status monitor + prefs.setValue(IBugzillaConstants.STATUS_VALUES, + queryOptionsToString(parser.getStatusValues())); + monitor.worked(1); + + // get the new values for the preselected status values and increment the status monitor + prefs.setValue(IBugzillaConstants.PRESELECTED_STATUS_VALUES, + queryOptionsToString(parser.getPreselectedStatusValues())); + monitor.worked(1); + + // get the new values for the resolution field and increment the status monitor + prefs.setValue(IBugzillaConstants.RESOLUTION_VALUES, + queryOptionsToString(parser.getResolutionValues())); + monitor.worked(1); + + // get the new values for the severity field and increment the status monitor + prefs.setValue(IBugzillaConstants.SEVERITY_VALUES, + queryOptionsToString(parser.getSeverityValues())); + monitor.worked(1); + + // get the new values for the priority field and increment the status monitor + prefs.setValue(IBugzillaConstants.PRIORITY_VALUES, + queryOptionsToString(parser.getPriorityValues())); + monitor.worked(1); + + // get the new values for the hardware field and increment the status monitor + prefs.setValue(IBugzillaConstants.HARDWARE_VALUES, + queryOptionsToString(parser.getHardwareValues())); + monitor.worked(1); + + // get the new values for the OS field and increment the status monitor + prefs.setValue(IBugzillaConstants.OS_VALUES, + queryOptionsToString(parser.getOSValues())); + monitor.worked(1); + + // get the new values for the product field and increment the status monitor + prefs.setValue(IBugzillaConstants.PRODUCT_VALUES, + queryOptionsToString(parser.getProductValues())); + monitor.worked(1); + + // get the new values for the component field and increment the status monitor + prefs.setValue(IBugzillaConstants.COMPONENT_VALUES, + queryOptionsToString(parser.getComponentValues())); + monitor.worked(1); + + // get the new values for the version field and increment the status monitor + prefs.setValue(IBugzillaConstants.VERSION_VALUES, + queryOptionsToString(parser.getVersionValues())); + monitor.worked(1); + + // get the new values for the target field and increment the status monitor + prefs.setValue(IBugzillaConstants.TARGET_VALUES, + queryOptionsToString(parser.getTargetValues())); + monitor.worked(1); + } + + /** + * Set the default query options for the bugzilla search + */ + private static void setDefaultQueryOptions() { + // get the preferences store for the bugzilla preferences + IPreferenceStore prefs = BugzillaPlugin.getDefault().getPreferenceStore(); + + // get the default status values from the store and set them as the default options + prefs.setDefault(IBugzillaConstants.STATUS_VALUES, + queryOptionsToString(IBugzillaConstants.DEFAULT_STATUS_VALUES)); + + // get the default preselected status values from the store and set them as the default options + prefs.setDefault(IBugzillaConstants.PRESELECTED_STATUS_VALUES, + queryOptionsToString(IBugzillaConstants.DEFAULT_PRESELECTED_STATUS_VALUES)); + + // get the default resolution values from the store and set them as the default options + prefs.setDefault(IBugzillaConstants.RESOLUTION_VALUES, + queryOptionsToString(IBugzillaConstants.DEFAULT_RESOLUTION_VALUES)); + + // get the default severity values from the store and set them as the default options + prefs.setDefault(IBugzillaConstants.SEVERITY_VALUES, + queryOptionsToString(IBugzillaConstants.DEFAULT_SEVERITY_VALUES)); + + // get the default priority values from the store and set them as the default options + prefs.setDefault(IBugzillaConstants.PRIORITY_VALUES, + queryOptionsToString(IBugzillaConstants.DEFAULT_PRIORITY_VALUES)); + + // get the default hardware values from the store and set them as the default options + prefs.setDefault(IBugzillaConstants.HARDWARE_VALUES, + queryOptionsToString(IBugzillaConstants.DEFAULT_HARDWARE_VALUES)); + + // get the default os values from the store and set them as the default options + prefs.setDefault(IBugzillaConstants.OS_VALUES, + queryOptionsToString(IBugzillaConstants.DEFAULT_OS_VALUES)); + + // get the default product values from the store and set them as the default options + prefs.setDefault(IBugzillaConstants.PRODUCT_VALUES, + queryOptionsToString(IBugzillaConstants.DEFAULT_PRODUCT_VALUES)); + + // get the default component values from the store and set them as the default options + prefs.setDefault(IBugzillaConstants.COMPONENT_VALUES, + queryOptionsToString(IBugzillaConstants.DEFAULT_COMPONENT_VALUES)); + + // get the default version values from the store and set them as the default options + prefs.setDefault(IBugzillaConstants.VERSION_VALUES, + queryOptionsToString(IBugzillaConstants.DEFAULT_VERSION_VALUES)); + + // get the default target values from the store and set them as the default options + prefs.setDefault(IBugzillaConstants.TARGET_VALUES, + queryOptionsToString(IBugzillaConstants.DEFAULT_TARGET_VALUES)); + } + + /** + * Turn the array of query options into a string separated by a '!' + * + * @param array A string array of query values to be turned into a string + * @return The string containing the query options in the array + */ + private static String queryOptionsToString(String[] array) { + // make a new string buffer and go through each element in the array + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < array.length; i++) { + // append the new value to the end and add a '!' as a delimiter + buffer.append(array[i]); + buffer.append("!"); + } + + // return the buffer converted to a string + return buffer.toString(); + } + + /** + * Take a string of query options and convert it to an array + * + * @param values A string of query options delimited by a '!' + * @return A string array containing the query values + */ + public static String[] queryOptionsToArray(String values) { + // create a new string buffer and array list + StringBuffer buffer = new StringBuffer(); + List<String> options = new ArrayList<String>(); + + // make the string into a character array + char[] chars = values.toCharArray(); + + // go through each of the characters in the character array + for (int i = 0; i < chars.length; i++) { + if (chars[i] == '!') { + // if the character is the delimiting value add the buffer to the list + // and reinitialize it + options.add(buffer.toString()); + buffer = new StringBuffer(); + } else { + // if it is a regular character, just add it to the string buffer + buffer.append(chars[i]); + } + } + + // create a new string array with the same size as the array list + String[] array = new String[options.size()]; + + // put each element from the list into the array + for (int j = 0; j < options.size(); j++) + array[j] = options.get(j); + return array; + } + + /** + * Get the password and user name from the keyring + * + */ + @SuppressWarnings("unchecked") + private static void getCachedData() { + // get the map containing the password and username + Map<String, String> map = Platform.getAuthorizationInfo(FAKE_URL, "Bugzilla", AUTH_SCHEME); + + // get the information from the map and save it + if (map != null) { + String username = map.get(INFO_USERNAME); + + if (username != null) + user = username; + else + user = new String(""); + + String pwd = map.get(INFO_PASSWORD); + + if (pwd != null) + password = pwd; + else + password = new String(""); + + return; + } + + // if the map was null, set the username and password to be null + user = new String(""); + password = new String(""); + } + + /** + * Gets the bugzilla user name from the preferences + * + * @return The string containing the user name + */ + public static String getUserName() { + getCachedData(); + return user; + } + + /** + * Get whether we are dealing with Bugzilla 2.18 or not + * @return true if it is 218 + */ + public static boolean is218(){ + return BugzillaPlugin.getDefault().getPreferenceStore().getBoolean(IBugzillaConstants.IS_218); + } + + /** + * Gets the bugzilla password from the preferences + * + * @return The string containing the password + */ + public static String getPassword() { + getCachedData(); + return password; + } + + /** + * store the password and username in the keyring + * + * @param username The user name to store + * @param storePassword The password to store + * @param createIfAbsent Whether to create the map if it doesn't exist or not + */ + @SuppressWarnings("unchecked") + private static void storeCache(String username, String storePassword, + boolean createIfAbsent) { + // put the password into the Platform map + Map<String, String> map = Platform.getAuthorizationInfo(FAKE_URL, "Bugzilla", AUTH_SCHEME); + + // if the map doesn't exist, see if we can create a new one + if (map == null) { + if (!createIfAbsent) + return; + map = new java.util.HashMap<String, String>(10); + } + + // add the username and password to the map + if (username != null) + map.put(INFO_USERNAME, username); + if (storePassword != null) + map.put(INFO_PASSWORD, storePassword); + + try { + // write the map to the keyring + Platform.addAuthorizationInfo(FAKE_URL, "Bugzilla", AUTH_SCHEME, map); + } catch (CoreException e) { + BugzillaPlugin.log(e.getStatus()); + } + } + + private static String user; + + private static String password; + + public static final String INFO_PASSWORD = "org.eclipse.team.cvs.core.password"; //$NON-NLS-1$ + + public static final String INFO_USERNAME = "org.eclipse.team.cvs.core.username"; //$NON-NLS-1$ + + public static final String AUTH_SCHEME = ""; + + public static final URL FAKE_URL; + + static { + URL temp = null; + try { + temp = new URL("http://" + IBugzillaConstants.PLUGIN_ID); + } catch (MalformedURLException e) { + BugzillaPlugin.log(new Status(IStatus.WARNING, IBugzillaConstants.PLUGIN_ID,IStatus.OK,"Bad temp server url: BugzillaPreferences", e)); + } + FAKE_URL = temp; + } + +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/IBugzillaConstants.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/IBugzillaConstants.java new file mode 100644 index 000000000..9414c3caf --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/IBugzillaConstants.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla; + +/** + * @author kvesik + * + * Created on Mar 26, 2003 + */ + +/** + * Interface for holding Bugzilla constants. + */ +public interface IBugzillaConstants +{ + + // The bugzilla plugin id + static final String PLUGIN_ID = "org.eclipse.mylar.bugzilla"; + + // The id's of other bugzilla packages + static final String EXISTING_BUG_EDITOR_ID = PLUGIN_ID + ".ui.existingBugEditor"; + static final String NEW_BUG_EDITOR_ID = PLUGIN_ID + ".ui.newBugEditor"; + static final String SEARCH_PAGE_ID = PLUGIN_ID + ".search.bugzillaSearchPage"; + static final String SEARCH_PAGE_CONTEXT = PLUGIN_ID + ".bugzillaSearchContext"; + static final String EDITOR_PAGE_CONTEXT = PLUGIN_ID + ".bugzillaEditorContext"; + static final String HIT_MARKER_ID = PLUGIN_ID + ".searchHit"; + + // The is's for hit markers used in the label provider and sorters + static final String HIT_MARKER_ATTR_ID = "id"; + static final String HIT_MARKER_ATTR_HREF = "href"; + static final String HIT_MARKER_ATTR_DESC = "description"; + static final String HIT_MARKER_ATTR_LABEL = "label"; + static final String HIT_MARKER_ATTR_SEVERITY = "severity"; + static final String HIT_MARKER_ATTR_PRIORITY = "priority"; + static final String HIT_MARKER_ATTR_PLATFORM = "platform"; + static final String HIT_MARKER_ATTR_STATE = "state"; + static final String HIT_MARKER_ATTR_RESULT = "result"; + static final String HIT_MARKER_ATTR_OWNER = "owner"; + static final String HIT_MARKER_ATTR_QUERY = "query"; + + // Error code + static final int ERROR_CODE = 1; + + // Bugzilla Preferences keys + static final String BUGZILLA_SERVER = "BUGZILLA_SERVER"; + static final String MOST_RECENT_QUERY = "MOST_RECENT_QUERY"; + static final String IS_218 = "BUGZILLA_IS_218"; + + + // names for the resources used to hold the different attributes of a bug + static final String STATUS_VALUES = "STATUS_VALUES"; + static final String PRESELECTED_STATUS_VALUES = "PRESELECTED_STATUS_VALUES"; + static final String RESOLUTION_VALUES = "RESOLUTION_VALUES"; + static final String SEVERITY_VALUES = "SEVERITY_VALUES"; + static final String PRIORITY_VALUES = "PRIORITY_VALUES"; + static final String HARDWARE_VALUES = "HARDWARE_VALUES"; + static final String OS_VALUES = "OS_VALUES"; + static final String PRODUCT_VALUES = "PRODUCT_VALUES"; + static final String COMPONENT_VALUES = "COMPONENT_VALUES"; + static final String VERSION_VALUES = "VERSION_VALUES"; + static final String TARGET_VALUES = "TARGET_VALUES"; + + // Default values for keys + static final String DEFAULT_BUGZILLA_SERVER = "https://bugs.eclipse.org/bugs"; + + static final String[] DEFAULT_STATUS_VALUES = {"Unconfirmed", "New", "Assigned", "Reopened", "Resolved", "Verified", "Closed"}; + static final String[] DEFAULT_PRESELECTED_STATUS_VALUES = {"New", "Assigned", "Reopened"}; + static final String[] DEFAULT_RESOLUTION_VALUES = {"Fixed", "Invalid", "Wontfix", "Later", "Remind", "Duplicate", "Worksforme", "Moved"}; + static final String[] DEFAULT_SEVERITY_VALUES = {"blocker", "critical", "major", "normal", "minor", "trivial", "enhancement"}; + static final String[] DEFAULT_PRIORITY_VALUES = {"P1", "P2", "P3", "P4", "P5"}; + static final String[] DEFAULT_HARDWARE_VALUES = {"All", "Macintosh", "PC", "Power PC", "Sun", "Other"}; + static final String[] DEFAULT_OS_VALUES = {"All", "AIX Motif", "Windows 95", "Windows 98", "Windows CE", "Windows ME", "Windows 2000", + "Windows NT", "Windows XP", "Windows All", "MacOS X", "Linux", "Linux-GTK", "Linux-Motif", "HP-UX", "Neutrino", + "QNX-Photon", "Solaris", "Unix All", "other"}; + static final String[] DEFAULT_PRODUCT_VALUES = {"AJDT", "AspectJ", "CDT", "EMF", "Equinox", "GEF", "JDT", "PDE", "Platform", "Stellation", "XSD"}; + static final String[] DEFAULT_COMPONENT_VALUES = {"Access Control", "Ant", "Commandline", "Compare", "Compiler", "Core", "Cpp-Extensions", "Debug", + "Doc", "Docs", "draw2d", "Dynamic Plugins", "Fine-Grained", "GEF", "Generic-Extensions", "Help", "IDE", "Launcher", "LPEX", "Plugins", + "Releng", "Repository", "Script Tests", "Scripting", "Search", "Server", "SWT", "Text", "UI", "Unit Tests", "Update", "VCM", + "WebDAV", "Windows Support"}; + static final String[] DEFAULT_VERSION_VALUES = {"0.5", "1.0", "2.0", "2.0.1", "2.0.2", "2.1", "2.2", "unspecified"}; + static final String[] DEFAULT_TARGET_VALUES = {"2.0 M1", "Alpha1", "Alpha2", "Alpha3", "Alpha4", "1.0", "1.0 - 20020308", "1.0 - Release", + "2.0 M2", "2.0 - 20020308", "2.0 M3", "2.0 - 20020408", "2.0 M4", "2.0 - 20020508", "2.0 M5", "2.0 - Release", "2.0 M6", + "2.0 F1", "2.0 F2", "2.0 F3", "2.0 F4", "2.0.1", "2.0.2", + "2.1", "2.1 M1", "2.1 M2", "2.1 M3", "2.1 M4", "2.1 M5", "2.1 RC1", "2.1 RC2", "2.1 RC3", "2.2", "Future"}; +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/TrustAll.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/TrustAll.java new file mode 100644 index 000000000..f131cd68f --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/TrustAll.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla; + +import javax.net.ssl.X509TrustManager; + +/** +* TrustAll class implements X509TrustManager to access all https servers with signed and +* unsigned certificates. +*/ +public class TrustAll implements X509TrustManager +{ + // seems to be no purpose + public boolean checkClientTrusted(java.security.cert.X509Certificate[] chain) + { + return true; + } + + // seems to be no purpose + public boolean isServerTrusted(java.security.cert.X509Certificate[] chain) + { + return true; + } + + // seems to be no purpose + public boolean isClientTrusted(java.security.cert.X509Certificate[] chain) + { + return true; + } + + /** + * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers() + */ + public java.security.cert.X509Certificate[] getAcceptedIssuers() + { + return null; + } + + /** + * @see javax.net.ssl.X509TrustManager#checkClientTrusted(java.security.cert.X509Certificate[], java.lang.String) + */ + public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) + { + // don't need to do any checks + } + + /** + * @see javax.net.ssl.X509TrustManager#checkServerTrusted(java.security.cert.X509Certificate[], java.lang.String) + */ + public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) + { + // don't need to do any checks + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/compare/BugzillaCompareInput.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/compare/BugzillaCompareInput.java new file mode 100644 index 000000000..f81a6e272 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/compare/BugzillaCompareInput.java @@ -0,0 +1,120 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.compare; + +import org.eclipse.compare.CompareConfiguration; +import org.eclipse.compare.CompareEditorInput; +import org.eclipse.compare.structuremergeviewer.Differencer; +import org.eclipse.compare.structuremergeviewer.IStructureComparator; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.mylar.bugzilla.core.BugReport; + + +/** + * A two-way or three-way compare for <code>BugReport</code> objects. + */ +public class BugzillaCompareInput extends CompareEditorInput { + + private boolean threeWay= false; + private Object root; + private IStructureComparator ancestor = null; + private IStructureComparator left = null; + private IStructureComparator right = null; + + /** + * Constructor. + * + * @param configuration + * The compare configuration used in this compare input. + * @see CompareConfiguration + */ + public BugzillaCompareInput(CompareConfiguration configuration) { + super(configuration); + } + + @Override + protected Object prepareInput(IProgressMonitor monitor) { + if (left == null || right == null) { + return null; + } + Differencer d= new Differencer(); + root= d.findDifferences(threeWay, monitor, null, ancestor, left, right); + return root; + } + + /** + * @return The original object that's to be compared (appears on the top of + * the compare view). + */ + public IStructureComparator getAncestor() { + return ancestor; + } + + /** + * Sets the original object that's to be compared (appears on the top of the + * compare view). + * + * @param newAncestor + * The new original object. + */ + public void setAncestor(BugReport newAncestor) { + threeWay = (newAncestor != null); + BugzillaStructureCreator structureCreator = new BugzillaStructureCreator(); + ancestor = structureCreator.getStructure(newAncestor); + } + + /** + * @return The local object that's to be compared (appears on the left side + * of the compare view). + */ + public IStructureComparator getLeft() { + return left; + } + + /** + * Sets the local object that's to be compared (appears on the left side of + * the compare view). + * + * @param newLeft + * The new local object. + */ + public void setLeft(BugReport newLeft) { + BugzillaStructureCreator structureCreator = new BugzillaStructureCreator(); + left = structureCreator.getStructure(newLeft); + } + + /** + * @return The online object that's to be compared (appears on the right + * side of the compare view). + */ + public IStructureComparator getRight() { + return right; + } + + /** + * Sets the online object that's to be compared (appears on the right side + * of the compare view). + * + * @param newRight + * The new online object. + */ + public void setRight(BugReport newRight) { + BugzillaStructureCreator structureCreator = new BugzillaStructureCreator(); + right = structureCreator.getStructure(newRight); + } + + /** + * @return <code>true</code> if a three-way comparison is to be done. + */ + public boolean isThreeWay() { + return threeWay; + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/compare/BugzillaCompareNode.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/compare/BugzillaCompareNode.java new file mode 100644 index 000000000..f8ca4be04 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/compare/BugzillaCompareNode.java @@ -0,0 +1,232 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.compare; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Iterator; + +import org.eclipse.compare.IStreamContentAccessor; +import org.eclipse.compare.ITypedElement; +import org.eclipse.compare.structuremergeviewer.IStructureComparator; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.mylar.bugzilla.core.Attribute; +import org.eclipse.mylar.bugzilla.core.BugReport; +import org.eclipse.mylar.bugzilla.core.Comment; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.ISharedImages; +import org.eclipse.ui.PlatformUI; + + +/** + * A node for the tree used to compare bugs in the compare viewer. + */ +public class BugzillaCompareNode implements IStreamContentAccessor, IStructureComparator, ITypedElement { + + /** The label for this piece of data. */ + private String key; + + /** The data for this node. */ + private String value; + + /** The children of this node. */ + private ArrayList<BugzillaCompareNode> nodeChildren; + + /** This node's image. */ + private Image image; + + /** + * Constructor. The image for this node is set to <code>null</code>. + * @param key The label for this node. + * @param value The data for this node. + */ + public BugzillaCompareNode(String key, String value) { + this(key, value, null); + } + + /** + * Constructor. + * @param key The label for this node. + * @param value The data for this node. + * @param image The image for this node. + */ + public BugzillaCompareNode(String key, String value, Image image) { + super(); + this.key = key; + this.value = checkText(value); + this.nodeChildren = null; + this.image = image; + } + + /** + * This function checks to make sure the given string is not + * <code>null</code>. If it is, the empty string is returned instead. + * + * @param newValue + * The string to be checked. + * @return If the text is <code>null</code>, then return the null string (<code>""</code>). + * Otherwise, return the text. + */ + private String checkText(String newValue) { + return ((newValue == null) ? "" : newValue); + } + + public Object[] getChildren() { + return (nodeChildren == null) ? new Object[0] : nodeChildren.toArray(); + } + + /** + * Adds a node to this node's list of children. + * @param bugNode The new child. + */ + public void addChild(BugzillaCompareNode bugNode) { + if (nodeChildren == null) { + nodeChildren = new ArrayList<BugzillaCompareNode>(); + } + nodeChildren.add(bugNode); + } + + public InputStream getContents() throws CoreException { + return new ByteArrayInputStream(getValue().getBytes()); + } + + /** + * @return The label for this node. + */ + public String getKey() { + return key; + } + + /** + * Set the label for this node. + * + * @param key + * The new label. + */ + public void setKey(String key) { + this.key = key; + } + + /** + * @return The data for this node. + */ + public String getValue() { + return value; + } + + /** + * Set the data for this node. + * + * @param value + * The new data. + */ + public void setValue(String value) { + this.value = checkText(value); + } + + public Image getImage() { + return image; + } + + /** + * Sets the image for this object. This image is used when displaying this + * object in the UI. + * + * @param newImage + * The new image. + */ + public void setImage(Image newImage) { + this.image = newImage; + } + + @Override + public boolean equals(Object arg0) { + if (arg0 instanceof BugzillaCompareNode) { + BugzillaCompareNode bugNode = (BugzillaCompareNode) arg0; + return getKey().equals(bugNode.getKey()); + } + return super.equals(arg0); + } + + @Override + public int hashCode() { + return getKey().hashCode(); + } + + + public String getName() { + return getKey(); + } + + public String getType() { + return "bug report"; + } + + /** + * Parses the given <code>BugReport</code> into a tree of + * <code>BugzillaCompareNode</code>'s suitable for use in a compare + * viewer. + * + * @param bug + * The <code>BugReport</code> that needs parsing. + * @return The tree of <code>BugzillaCompareNode</code>'s. + */ + public static BugzillaCompareNode parseBugReport(BugReport bug) { + Image defaultImage = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_DEF_VIEW); + BugzillaCompareNode topNode = new BugzillaCompareNode("Bug #" + bug.getId(), null, defaultImage); + topNode.addChild(new BugzillaCompareNode("Creation Date", bug.getCreated().toString(), defaultImage)); + + String keywords = ""; + if (bug.getKeywords() != null) { + for (Iterator<String> iter = bug.getKeywords().iterator(); iter.hasNext();) { + keywords += iter.next() + " "; + } + } + topNode.addChild(new BugzillaCompareNode("Keywords", keywords, defaultImage)); + + Image attributeImage = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_ELEMENT); + BugzillaCompareNode attributes = new BugzillaCompareNode("Attributes", null, attributeImage); + for (Iterator<Attribute> iter = bug.getAttributes().iterator(); iter.hasNext();) { + Attribute attribute = iter.next(); + + // Since the bug report may not be saved offline, get the attribute's new + // value, which is what is in the submit viewer. + attributes.addChild(new BugzillaCompareNode(attribute.getName(), attribute.getNewValue(), attributeImage)); + } + topNode.addChild(attributes); + + topNode.addChild(new BugzillaCompareNode("Description", bug.getDescription(), defaultImage)); + + BugzillaCompareNode comments = new BugzillaCompareNode("Comments", null, defaultImage); + for (Iterator<Comment> iter = bug.getComments().iterator(); iter.hasNext();) { + Comment comment = iter.next(); + String bodyString = "Comment from " + comment.getAuthorName() + ":\n\n" + comment.getText(); + comments.addChild(new BugzillaCompareNode(comment.getCreated().toString(), bodyString, defaultImage)); + } + topNode.addChild(comments); + + topNode.addChild(new BugzillaCompareNode("New Comment", bug.getNewNewComment(), defaultImage)); + + BugzillaCompareNode ccList = new BugzillaCompareNode("CC List", null, defaultImage); + for (Iterator<String> iter = bug.getCC().iterator(); iter.hasNext();) { + String cc = iter.next(); + ccList.addChild(new BugzillaCompareNode("CC", cc, defaultImage)); + } + topNode.addChild(ccList); + + BugzillaCompareNode titleNode = new BugzillaCompareNode("BugReport Object", null, defaultImage); + titleNode.addChild(topNode); + + return titleNode; + } + +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/compare/BugzillaStructureCreator.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/compare/BugzillaStructureCreator.java new file mode 100644 index 000000000..7c55181c8 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/compare/BugzillaStructureCreator.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.compare; + +import org.eclipse.compare.structuremergeviewer.IStructureComparator; +import org.eclipse.compare.structuremergeviewer.IStructureCreator; +import org.eclipse.jface.util.Assert; +import org.eclipse.mylar.bugzilla.core.BugReport; + + +/** + * This implementation of the <code>IStructureCreator</code> interface + * makes the contents of a <code>BugReport</code> object available as a + * hierarchical structure of <code>IStructureComparator</code>s. + * <p> + * It is used when comparing a modified bug report to the one on the + * corresponding server. + */ +public class BugzillaStructureCreator implements IStructureCreator { + + /** + * Create a new BugzillaStructureCreator. + */ + public BugzillaStructureCreator() { + super(); + } + + public String getName() { + return "Bugzilla Structure Creator"; + } + + public IStructureComparator getStructure(Object input) { + if (input instanceof BugReport) { + BugReport bugReport = (BugReport) input; + return BugzillaCompareNode.parseBugReport(bugReport); + } + else { + return null; + } + } + + public IStructureComparator locate(Object path, Object input) { + return null; + } + + public String getContents(Object node, boolean ignoreWhitespace) { + if (node instanceof BugzillaCompareNode) { + String s = ((BugzillaCompareNode)node).getValue(); + if (ignoreWhitespace) + s= s.trim(); + return s; + } + return null; + } + + /** + * Called whenever a copy operation has been performed on a tree node. + * This implementation throws an <code>AssertionFailedException</code> + * since we cannot update a bug report object. + * + * @param structure the node for which to save the new content + * @param input the object from which the structure tree was created in <code>getStructure</code> + */ + public void save(IStructureComparator node, Object input) { + Assert.isTrue(false); // Cannot update bug report object + } + +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/Attribute.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/Attribute.java new file mode 100644 index 000000000..da69b5491 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/Attribute.java @@ -0,0 +1,177 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.core; + +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Class representing a Bugzilla report attribute that can be changed on + * the server. + */ +public class Attribute implements Serializable +{ + /** Automatically generated serialVersionUID */ + private static final long serialVersionUID = 3257009873370757424L; + + private boolean hidden = false; + + /** Attribute name */ + private String name; + + /** Name of the option used when updating the attribute on the server */ + private String parameterName; + + /** Legal values of the attribute */ + private LinkedHashMap<String, String> optionValues; + + /** Attribute's value (input field or selected option; value that is saved or from the server) */ + private String value; + + /** Attributes new Value (value chosen in submit editor) */ + private String newValue; + + /** + * Constructor + * + * @param name The name of the attribute + */ + public Attribute(String name) + { + // initialize the name and its legal values + this.name = name; + optionValues = new LinkedHashMap<String, String>(); + } + + /** + * Get the attribute's name + * + * @return The name of the attribute + */ + public String getName() + { + return name; + } + + /** + * Get name of the option used when updating the attribute on the server + * + * @return The name of the option for server updates + */ + public String getParameterName() + { + return parameterName; + } + + /** + * Get whether the attribute can be edited by the used + * + * @return <code>true</code> if the attribute can be edited by the user + */ + public boolean isEditable() + { + return optionValues.size() > 0; + } + + /** + * Get the legal values for the option + * + * @return The <code>Map</code> of legal values for the option. + */ + public Map<String, String> getOptionValues() + { + return optionValues; + } + + /** + * Get the value of the attribute + * + * @return A <code>String</code> of the attributes value + */ + public String getValue() + { + return value; + } + + /** + * Set the value of the attribute + * + * @param value The new value of the attribute + */ + public void setValue(String value) + { + this.value = value; + newValue = value; + } + + /** + * Set the new value of the attribute + * + * @param newVal The new value of the attribute + */ + public void setNewValue(String newVal) + { + newValue = newVal; + } + + /** + * Get the new value for the attribute + * + * @return The new value + */ + public String getNewValue() + { + return newValue; + } + + /** + * Sets the name of the option used when updating the attribute on the server + * + * @param parameterName The name of the option when updating from the server + */ + public void setParameterName(String parameterName) + { + this.parameterName = parameterName; + } + + /** + * Adds an attribute option value + * + * @param readableValue The value displayed on the screen + * @param parameterValue The option value used when sending the form to the server + */ + public void addOptionValue(String readableValue, String parameterValue) + { + optionValues.put(readableValue, parameterValue); + } + + /** + * Determine if the field was hidden or not + * + * @return True if the field was hidden + */ + public boolean isHidden() + { + return hidden; + } + + /** + * Set whether the field was hidden in the bug + * + * @param b Whether the field was hidden or not + */ + public void setHidden(boolean b) + { + hidden = b; + } + +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/BugPost.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/BugPost.java new file mode 100644 index 000000000..d8d97b174 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/BugPost.java @@ -0,0 +1,277 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.core; + + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.security.auth.login.LoginException; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.mylar.bugzilla.BugzillaPlugin; +import org.eclipse.mylar.bugzilla.IBugzillaConstants; +import org.eclipse.mylar.bugzilla.TrustAll; + + +/** + * + * @author Shawn Minto + * + * Class to handle the positing of a bug + */ +public class BugPost { + + /** The fields that are to be changed/maintained */ + Map<String, String> fields = new HashMap<String, String>(); + + /** The url to post the bug to */ + URL anURL; + + /** The prefix for how to find the bug number from the return */ + String prefix; + + /** The postfix for how to find the bug number from the return */ + String postfix1; + + /** An alternate postfix for how to find the bug number from the return */ + String postfix2; + + /** + * Add a value to be posted to the bug + * @param key The key for the value to be added + * @param value The value to be added + */ + public void add(String key, String value) { + try { + fields.put(key, URLEncoder.encode(value == null ? "" : value, "UTF-8")); + } + catch (UnsupportedEncodingException e) { + /* + * Do nothing. Every implementation of the Java platform is required + * to support the standard charset "UTF-8" + */ + } + } + + /** + * Set the url that the bug is supposed to be posted to + * @param urlString The url to post the bug to + */ + public void setURL(String urlString) throws MalformedURLException { + anURL = new URL(urlString); + } + + /** + * Post the bug to the bugzilla server + * @return The result of the responses + * @throws BugzillaException + */ + public String post() throws BugzillaException, LoginException { + return post(false); + } + + /** + * Post the bug to the bugzilla server + * @param isDebug Whether we are debugging or not - if it is debug, we get the respose printed to std out + * @throws BugzillaException + * @throws LoginException + */ + public String post(boolean isDebug) throws BugzillaException, LoginException { + BufferedOutputStream out = null; + BufferedReader in = null; + + try { + // connect to the bugzilla server + SSLContext ctx = SSLContext.getInstance("TLS"); + + javax.net.ssl.TrustManager[] tm = new javax.net.ssl.TrustManager[]{new TrustAll()}; + ctx.init(null, tm, null); + HttpsURLConnection.setDefaultSSLSocketFactory(ctx.getSocketFactory()); + HttpURLConnection postConnection = (HttpURLConnection) anURL.openConnection(); + + // set the connection method + postConnection.setRequestMethod("POST"); + postConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + // get the url for the update with all of the changed values + byte [] body = getPostBody().getBytes(); + postConnection.setRequestProperty("Content-Length", String.valueOf(body.length)); + + // allow outgoing streams and open a stream to post to + postConnection.setDoOutput(true); + + out = new BufferedOutputStream(postConnection.getOutputStream()); + + // print out debug methods if we are debugging + if (isDebug) { + System.out.println("SENDING: "); + System.out.println("URL: " + anURL); + + System.out.println("Body: \n" + new String(body)); + } + + // write the data and close the stream + out.write(body); + out.flush(); + + int responseCode = postConnection.getResponseCode(); + if (responseCode != HttpURLConnection.HTTP_OK && + responseCode != HttpURLConnection.HTTP_CREATED) { + throw new BugzillaException("Server returned HTTP error: " + responseCode + " - " + postConnection.getResponseMessage()); + } + + // open a stream to receive response from bugzilla + in = new BufferedReader(new InputStreamReader(postConnection.getInputStream())); + String result = null; + + if (isDebug) + System.out.println("RECEIVING:"); + String aString = in.readLine(); + + + while (aString != null) { + if (isDebug) + System.out.println(aString); + + // check if we have run into an error + if(result == null && (aString.toLowerCase().indexOf("check e-mail") != -1 || aString.toLowerCase().indexOf("error") != -1)) + { + throw new LoginException("Bugzilla login information incorrect"); + } + + // get the bug number if it is required + if (prefix != null && postfix1 != null && postfix2 != null && result == null) { + int startIndex = aString.toLowerCase().indexOf(prefix.toLowerCase()); + if (startIndex > -1) { + startIndex = startIndex + prefix.length(); + int stopIndex = aString.toLowerCase().indexOf(postfix1.toLowerCase(), startIndex); + if(stopIndex == -1) + stopIndex = aString.toLowerCase().indexOf(postfix2.toLowerCase(), startIndex); + if (stopIndex > -1) { + result = (aString.substring(startIndex, stopIndex)).trim(); + if (!isDebug) { + break; + } + } + } + } + aString = in.readLine(); + } + + // return the bug number + return result; + } catch (IOException e) { + throw new BugzillaException("An exception occurred while submitting the bug: " + e.getMessage(), e); + } catch (KeyManagementException e) + { + throw new BugzillaException("Could not POST form. Communications error: " + e.getMessage(), e); + } catch (NoSuchAlgorithmException e) + { + throw new BugzillaException("Could not POST form. Communications error: " + e.getMessage(), e); + } + finally + { + try{ + if(in != null) + in.close(); + if(out != null) + out.close(); + + }catch(IOException e) + { + BugzillaPlugin.log(new Status(IStatus.ERROR, IBugzillaConstants.PLUGIN_ID,IStatus.ERROR,"Problem posting the bug", e)); + } + } + } + + + /** + * Get the url that contains the attributes to be posted + * @return The url for posting + */ + private String getPostBody() { + String postBody = ""; + + // go through all of the attributes and add them to the body of the post + Iterator<Map.Entry<String, String>> anIterator = fields.entrySet().iterator(); + while (anIterator.hasNext()) { + Map.Entry<String, String> anEntry = anIterator.next(); + postBody = postBody + anEntry.getKey() + "=" + anEntry.getValue(); + if (anIterator.hasNext()) + postBody = postBody + "&"; + } + return postBody; + } + + /** + * Gets the prefix + * @return Returns a String + */ + public String getPrefix() { + return prefix; + } + + /** + * Sets the prefix + * @param prefix The prefix to set + */ + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + /** + * Gets the postfix + * @return Returns a String + */ + public String getPostfix1() { + return postfix1; + } + + /** + * Sets the postfix + * @param postfix The postfix to set + */ + public void setPostfix1(String postfix) { + this.postfix1 = postfix; + } + + /** + * Gets the alternate postfix + * @return Returns a String + */ + public String getPostfix2() { + return postfix2; + } + + /** + * Sets the alternate postfix + * @param postfix The postfix to set + */ + public void setPostfix2(String postfix) { + this.postfix2 = postfix; + } +} + diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/BugReport.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/BugReport.java new file mode 100644 index 000000000..223e15f04 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/BugReport.java @@ -0,0 +1,413 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.core; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.mylar.bugzilla.BugzillaPlugin; +import org.eclipse.mylar.bugzilla.IBugzillaConstants; +import org.eclipse.mylar.bugzilla.ui.editor.ExistingBugEditor; +import org.eclipse.mylar.bugzilla.ui.editor.ExistingBugEditorInput; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IWorkbenchPage; + + +/** + * A bug report entered in Bugzilla. + */ +public class BugReport implements Serializable, IBugzillaBug { + /** + * Comment for <code>serialVersionUID</code> + */ + private static final long serialVersionUID = 3258693199936631348L; + + /** Bug id */ + protected final int id; + + /** The bug's server */ + protected final String server; + + /** Description of the bug */ + protected String description; + + /** Creation timestamp */ + protected Date created; + + /** The bugs valid keywords */ + protected List<String> validKeywords; + + /** The operations that can be done on the bug*/ + protected List<Operation> operations = new ArrayList<Operation>(); + + /** Bug attributes (status, resolution, etc.) */ + protected HashMap<String, Attribute> attributes = new HashMap<String, Attribute>(); + + /** The keys for the bug attributes */ + protected ArrayList<String> attributeKeys = new ArrayList<String>(); + + /** A list of comments */ + protected ArrayList<Comment> comments = new ArrayList<Comment>(); + + /** The value for the new comment to add (text that is saved)*/ + protected String newComment = ""; + + /** The new value for the new comment to add (text from submit editor)*/ + protected String newNewComment = ""; + + /** CC list */ + protected HashSet<String> cc = new HashSet<String>(); + + /** The operation that was selected to do to the bug */ + protected Operation selectedOperation = null; + + /** Whether or not this bug report is saved offline. */ + protected boolean savedOffline = false; + + /** + * Constructor + * @param id The id of the bug + * @param server The server that this bug is being created for + */ + public BugReport(int id, String server) { + this.id = id; + this.server = server; + } + + /** + * Get the bugs id + * @return The bugs id + */ + public int getId() { + return id; + } + + public String getServer() { + return server; + } + + public String getLabel() { + return "Bug #" + id; + } + + /** + * Get the bugs description + * @return The description of the bug + */ + public String getDescription() { + return description; + } + + /** + * Set the description of the bug + * @param description The description to set the bug to have + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * Get the summary for the bug + * @return The bugs summary + */ + public String getSummary() { + if(getAttribute("Summary") == null){ + BugzillaPlugin.log(new Status(IStatus.ERROR, IBugzillaConstants.PLUGIN_ID, IStatus.ERROR, "WE SHOULD NEVER GET HERE " + id, null)); + return ""; + } + return getAttribute("Summary").getValue(); + } + + /** + * Set the summary of the bug + * @param summary The summary to set the bug to have + */ + public void setSummary(String summary) { + if( this.getAttribute("Summary") == null ){ + Attribute a = new Attribute("Summary"); + a.setValue(summary); + addAttribute(a); + } + else + getAttribute("Summary").setValue(summary); + } + + /** + * Get the date that the bug was created + * @return The bugs creation date + */ + public Date getCreated() { + return created; + } + + /** + * Set the bugs creation date + * @param created The date the the bug was created + */ + public void setCreated(Date created) { + this.created = created; + } + + public Attribute getAttribute(String key) { + return attributes.get(key); + } + + /** + * Get the list of attributes for this bug + * @return An <code>ArrayList</code> of the bugs attributes + */ + public List<Attribute> getAttributes() { + // create an array list to store the attributes in + ArrayList<Attribute> attributeEntries = new ArrayList<Attribute>(attributeKeys.size()); + + // go through each of the attribute keys + for (Iterator<String> it = attributeKeys.iterator(); it.hasNext();) { + // get the key for the attribute + String key = it.next(); + + // get the attribute and add it to the list + Attribute attribute = attributes.get(key); + attributeEntries.add(attribute); + } + + // return the list of attributes for the bug + return attributeEntries; + } + + /** + * Add an attribute to the bug + * @param attribute The attribute to add to the bug + */ + public void addAttribute(Attribute attribute) { + if (!attributes.containsKey(attribute.getName())) { + // add the attributes key to the list if it doesn't exist + attributeKeys.add(attribute.getName()); + } + + // put the value of the attribute into the map, using its name as the key + attributes.put(attribute.getName(), attribute); + } + + /** + * Get the comments posted on the bug + * @return A list of comments for the bug + */ + public ArrayList<Comment> getComments() { + return comments; + } + + /** + * Add a comment to the bug + * @param comment The comment to add to the bug + */ + public void addComment(Comment comment) { + Comment preceding = null; + if (comments.size() > 0) { + // if there are some comments, get the last comment and set the next + // value to be the new comment + preceding = comments.get(comments.size() - 1); + preceding.setNext(comment); + } + // set the comments previous value to the preceeding one + comment.setPrevious(preceding); + + // add the comment to the comment list + comments.add(comment); + } + + /** + * Get the person who reported the bug + * @return The person who reported the bug + */ + public String getReporter() { + return getAttribute("Reporter").getValue(); + } + + /** + * Get the person to whom this bug is assigned + * @return The person who is assigned to this bug + */ + public String getAssignedTo() { + return getAttribute("Assigned To").getValue(); + } + + /** + * Get the resolution of the bug + * @return The resolution of the bug + */ + public String getResolution() { + return getAttribute("Resolution").getValue(); + } + + /** + * Get the status of the bug + * @return The bugs status + */ + public String getStatus() { + return getAttribute("Status").getValue(); + } + + /** + * Get the keywords for the bug + * @return The keywords for the bug + */ + public List<String> getKeywords() { + return validKeywords; + } + + /** + * Set the keywords for the bug + * @param keywords The keywords to set the bug to have + */ + public void setKeywords(List<String> keywords) { + this.validKeywords = keywords; + } + + /** + * Get the set of addresses in the CC list + * @return A <code>Set</code> of addresses in the CC list + */ + public Set<String> getCC() { + return cc; + } + + /** + * Add an email to the bugs CC list + * @param email The email address to add to the CC list + */ + public void addCC(String email) { + cc.add(email); + } + + /** + * Remove an address from the bugs CC list + * @param email the address to be removed from the CC list + * @return <code>true</code> if the email is in the set and it was removed + */ + public boolean removeCC(String email) { + return cc.remove(email); + } + /** + * Get the new comment that is to be added to the bug + * @return The new comment + */ + public String getNewComment() { + return newComment; + } + + /** + * Set the new comment that will be added to the bug + * @param newComment The new comment to add to the bug + */ + public void setNewComment(String newComment) { + this.newComment = newComment; + newNewComment = newComment; + } + + /** + * Get the new value of the new NewComment + * @return the new value of the new NewComment. + */ + public String getNewNewComment() { + return newNewComment; + } + + + /** + * Set the new value of the new NewComment + * @param newNewComment The new value of the new NewComment. + */ + public void setNewNewComment(String newNewComment) { + this.newNewComment = newNewComment; + } + + + /** + * Get all of the operations that can be done to the bug + * @return The operations that can be done to the bug + */ + public List<Operation> getOperations() { + return operations; + } + + /** + * Add an operation to the bug + * @param o The operation to add + */ + public void addOperation(Operation o) { + operations.add(o); + } + + /** + * Get an operation from the bug based on its display name + * @param displayText The display text for the operation + * @return The operation that has the display text + */ + public Operation getOperation(String displayText) { + Iterator<Operation> itr = operations.iterator(); + while (itr.hasNext()) { + Operation o = itr.next(); + if (o.getOperationName().equals(displayText)) + return o; + } + return null; + } + + /** + * Set the selected operation + * @param o The selected operation + */ + public void setSelectedOperation(Operation o) { + selectedOperation = o; + } + + /** + * Get the selected operation + * @return The selected operation + */ + public Operation getSelectedOperation() { + return selectedOperation; + } + + public boolean isSavedOffline() { + return savedOffline; + } + + public boolean isLocallyCreated() { + return false; + } + + public void setOfflineState(boolean newOfflineState) { + savedOffline = newOfflineState; + } + + public void closeEditor(IWorkbenchPage page) { + IEditorInput input = new ExistingBugEditorInput(this); + IEditorPart bugEditor = page.findEditor(input); + if (bugEditor != null) { + page.closeEditor(bugEditor, false); + IEditorPart compareEditor = page.findEditor(((ExistingBugEditor)bugEditor).getCompareInput()); + if (compareEditor != null) { + page.closeEditor(compareEditor, false); + } + } + } + +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/BugzillaException.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/BugzillaException.java new file mode 100644 index 000000000..f4d951317 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/BugzillaException.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.core; + +import java.io.PrintStream; +import java.io.PrintWriter; + +public class BugzillaException extends Exception { + + /** Automatically generated serialVersionUID */ + private static final long serialVersionUID = 3257849887386449974L; + + private Throwable cause; + + /** + * Constructor for BugzillaException. + */ + public BugzillaException() { + super(); + } + + /** + * Constructor for BugzillaException. + * @param detailMessage + */ + public BugzillaException(String detailMessage) { + super(detailMessage); + } + + public BugzillaException(String detailMessage,Throwable cause) { + super(detailMessage); + this.cause = cause; + } + + public BugzillaException(Throwable cause) { + this.cause = cause; + } + + @Override + public synchronized void printStackTrace(PrintStream err) { + super.printStackTrace(err); + if (cause != null) { + err.println("\n--- Cause was:"); + cause.printStackTrace(err); + } + } + + @Override + public synchronized void printStackTrace(PrintWriter err) { + super.printStackTrace(err); + if (cause != null) { + err.println("\n--- Cause was:"); + cause.printStackTrace(err); + } + } + +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/BugzillaRepository.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/BugzillaRepository.java new file mode 100644 index 000000000..ee49173a1 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/BugzillaRepository.java @@ -0,0 +1,397 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.core; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.security.auth.login.LoginException; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.mylar.bugzilla.BugzillaPlugin; +import org.eclipse.mylar.bugzilla.BugzillaPreferences; +import org.eclipse.mylar.bugzilla.IBugzillaConstants; +import org.eclipse.mylar.bugzilla.TrustAll; +import org.eclipse.mylar.bugzilla.core.internal.BugParser; +import org.eclipse.mylar.bugzilla.core.internal.NewBugParser; +import org.eclipse.mylar.bugzilla.core.internal.ProductParser; +import org.eclipse.mylar.bugzilla.offlineReports.OfflineReportsFile; +import org.eclipse.mylar.bugzilla.ui.wizard.NewBugModel; + + +/** + * Singleton class that creates <code>BugReport</code> objects by fetching + * bug's state and contents from the Bugzilla server. + */ +public class BugzillaRepository +{ + + /** + * Test method. + */ + public static void main(String[] args) throws Exception { + instance = + new BugzillaRepository(BugzillaPlugin.getDefault().getServerName() + "/long_list.cgi?buglist="); + BugReport bug = instance.getBug(16161); + System.out.println("Bug " + bug.getId() + ": " + bug.getSummary()); + for (Iterator<Attribute> it = bug.getAttributes().iterator(); it.hasNext();) { + Attribute attribute = it.next(); + System.out.println(attribute.getName() + ": " + attribute.getValue()); + } + System.out.println(bug.getDescription()); + for (Iterator<Comment> it = bug.getComments().iterator(); it.hasNext();) { + Comment comment = it.next(); + System.out.println(comment.getAuthorName() + "<" + comment.getAuthor() + "> (" + comment.getCreated() + ")"); + System.out.print(comment.getText()); + System.out.println(); + } + } + + /** URL of the Bugzilla server */ + private static String bugzillaUrl; + + /** singleton instance */ + private static BugzillaRepository instance; + + /** + * Constructor + * @param bugzillaUrl - the url of the bugzilla repository + */ + private BugzillaRepository(String bugzillaUrl) + { + BugzillaRepository.bugzillaUrl = bugzillaUrl; + } + + /** + * Get the singleton instance of the <code>BugzillaRepository</code> + * @return The instance of the repository + */ + public synchronized static BugzillaRepository getInstance() + { + if (instance == null) + { + // if the instance hasn't been created yet, create one + instance = new BugzillaRepository( + BugzillaPlugin.getDefault().getServerName()); + } + + // fix bug 58 by updating url if it changes + if(! BugzillaRepository.bugzillaUrl.equals(BugzillaPlugin.getDefault().getServerName())) + { + BugzillaRepository.bugzillaUrl = BugzillaPlugin.getDefault().getServerName(); + } + + return instance; + } + + /** + * Get a bug from the server + * @param id - the id of the bug to get + * @return - a <code>BugReport</code> for the selected bug or null if it doesn't exist + * @throws IOException + */ + public BugReport getBug(int id) throws IOException, MalformedURLException, LoginException + { + + BufferedReader in = null; + try { + // connect to the bugzilla server + SSLContext ctx = SSLContext.getInstance("TLS"); + + javax.net.ssl.TrustManager[] tm = new javax.net.ssl.TrustManager[]{new TrustAll()}; + ctx.init(null, tm, null); + HttpsURLConnection.setDefaultSSLSocketFactory(ctx.getSocketFactory()); + + // create a new input stream for getting the bug + + String url = bugzillaUrl + "/show_bug.cgi?id=" + id; + + // allow the use to only see the operations that they can do to a bug if they have + // their user name and password in the preferences + if(BugzillaPreferences.getUserName() != null && !BugzillaPreferences.getUserName().equals("") && BugzillaPreferences.getPassword() != null && !BugzillaPreferences.getPassword().equals("")) + { + /* + * The UnsupportedEncodingException exception for + * URLEncoder.encode() should not be thrown, since every + * implementation of the Java platform is required to support + * the standard charset "UTF-8" + */ + url += "&GoAheadAndLogIn=1&Bugzilla_login=" + URLEncoder.encode(BugzillaPreferences.getUserName(), "UTF-8") + "&Bugzilla_password=" + URLEncoder.encode(BugzillaPreferences.getPassword(), "UTF-8"); + } + + in = new BufferedReader(new InputStreamReader(new URL(url).openStream())); + + // get the actual bug fron the server and return it + BugReport bug = BugParser.parseBug(in, id, BugzillaPlugin.getDefault().getServerName(), BugzillaPreferences.is218(), BugzillaPreferences.getUserName(), BugzillaPreferences.getPassword()); + + return bug; + } + catch (MalformedURLException e) { + throw e; + } + catch (IOException e) { + throw e; + } + catch(LoginException e) + { + throw e; + } + catch(Exception e) { + // throw an exception if there is a problem reading the bug from the server + throw new IOException(e.getMessage()); + } + finally + { + try{ + if(in != null) + in.close(); + }catch(IOException e) + { + BugzillaPlugin.log(new Status(IStatus.ERROR, IBugzillaConstants.PLUGIN_ID,IStatus.ERROR,"Problem closing the stream", e)); + } + } + } + + /** + * Get a bug from the server. + * If a bug with the given id is saved offline, the offline version is returned instead. + * @param id - the id of the bug to get + * @return - a <code>BugReport</code> for the selected bug or null if it doesn't exist + * @throws IOException, MalformedURLException, LoginException + */ + public BugReport getCurrentBug(int id) throws MalformedURLException, LoginException, IOException { + // Look among the offline reports for a bug with the given id. + OfflineReportsFile reportsFile = BugzillaPlugin.getDefault().getOfflineReports(); + int offlineId = reportsFile.find(id); + + // If an offline bug was found, return it if possible. + if (offlineId != -1) { + IBugzillaBug bug = reportsFile.elements().get(offlineId); + if (bug instanceof BugReport) { + return (BugReport)bug; + } + } + + // If a suitable offline report was not found, try to get one from the server. + return getBug(id); + } + + /** + * Get the list of products when creating a new bug + * @return The list of valid products a bug can be logged against + * @throws IOException + */ + public List<String> getProductList() throws IOException, LoginException, Exception + { + BufferedReader in = null; + try + { + // connect to the bugzilla server + SSLContext ctx = SSLContext.getInstance("TLS"); + + javax.net.ssl.TrustManager[] tm = new javax.net.ssl.TrustManager[]{new TrustAll()}; + ctx.init(null, tm, null); + HttpsURLConnection.setDefaultSSLSocketFactory(ctx.getSocketFactory()); + + String urlText = ""; + + // use the usename and password to get into bugzilla if we have it + if(BugzillaPreferences.getUserName() != null && !BugzillaPreferences.getUserName().equals("") && BugzillaPreferences.getPassword() != null && !BugzillaPreferences.getPassword().equals("")) + { + /* + * The UnsupportedEncodingException exception for + * URLEncoder.encode() should not be thrown, since every + * implementation of the Java platform is required to support + * the standard charset "UTF-8" + */ + urlText += "?GoAheadAndLogIn=1&Bugzilla_login=" + URLEncoder.encode(BugzillaPreferences.getUserName(), "UTF-8") + "&Bugzilla_password=" + URLEncoder.encode(BugzillaPreferences.getPassword(), "UTF-8"); + } + + URL url = new URL(bugzillaUrl + "/enter_bug.cgi"+urlText); + + // create a new input stream for getting the bug + in = new BufferedReader(new InputStreamReader(url.openStream())); + + + return new ProductParser(in).getProducts(); + } + finally + { + try{ + if(in != null) + in.close(); + }catch(IOException e) + { + BugzillaPlugin.log(new Status(IStatus.ERROR, IBugzillaConstants.PLUGIN_ID,IStatus.ERROR,"Problem closing the stream", e)); + } + } + } + + /** + * Get the attribute values for a new bug + * @param nbm A reference to a NewBugModel to store all of the data + * @throws Exception + */ + public void getnewBugAttributes(NewBugModel nbm, boolean getProd) throws Exception + { + BufferedReader in = null; + try + { + // connect to the bugzilla server + SSLContext ctx = SSLContext.getInstance("TLS"); + + javax.net.ssl.TrustManager[] tm = new javax.net.ssl.TrustManager[]{new TrustAll()}; + ctx.init(null, tm, null); + HttpsURLConnection.setDefaultSSLSocketFactory(ctx.getSocketFactory()); + + // create a new input stream for getting the bug + String prodname = URLEncoder.encode(nbm.getProduct(), "UTF-8"); + + String url = bugzillaUrl + "/enter_bug.cgi"; + + // use the proper url if we dont know the product yet + if(!getProd) + url += "?product=" + prodname + "&"; + else + url += "?"; + + // add the password and username to the url so that bugzilla logs us in + /* + * The UnsupportedEncodingException exception for + * URLEncoder.encode() should not be thrown, since every + * implementation of the Java platform is required to support + * the standard charset "UTF-8" + */ + url += "&GoAheadAndLogIn=1&Bugzilla_login=" + URLEncoder.encode(BugzillaPreferences.getUserName(), "UTF-8") + "&Bugzilla_password=" + URLEncoder.encode(BugzillaPreferences.getPassword(), "UTF-8"); + + in = new BufferedReader(new InputStreamReader(new URL(url).openStream())); + + new NewBugParser(in).parseBugAttributes(nbm, getProd); + + } catch(Exception e) { + + if ( e instanceof KeyManagementException || e instanceof NoSuchAlgorithmException || e instanceof IOException ){ + if(MessageDialog.openQuestion(null, "Bugzilla Connect Error", "Unable to connect to Bugzilla server.\n" + + "Bug report will be created offline and saved for submission later.")){ + nbm.setConnected(false); + getProdConfigAttributes(nbm); + } + else + throw new Exception("Bug report will not be created."); + } + else + throw e; + } + finally + { + try{ + if(in != null) + in.close(); + }catch(IOException e) + { + BugzillaPlugin.log(new Status(IStatus.ERROR, IBugzillaConstants.PLUGIN_ID,IStatus.ERROR,"Problem closing the stream", e)); + } + } + } + + /** + * Get the bugzilla url that the repository is using + * @return A <code>String</code> containing the url of the bugzilla server + */ + public static String getURL() + { + return bugzillaUrl; + } + + + /** Method to get attributes from ProductConfiguration if unable to connect + * to Bugzilla server + * @param model - the NewBugModel to store the attributes + */ + public void getProdConfigAttributes(NewBugModel model){ + + HashMap<String, Attribute> attributes = new HashMap<String, Attribute>(); + + // ATTRIBUTE: Severity + Attribute a = new Attribute("Severity"); + a.setParameterName("bug_severity"); + // get optionValues from ProductConfiguration + String[] optionValues = BugzillaPlugin.getDefault().getProductConfiguration().getSeverities(); + // add option values from ProductConfiguration to Attribute optionValues + for( int i=0; i<optionValues.length; i++ ){ + a.addOptionValue(optionValues[i], optionValues[i]); + } + // add Attribute to model + attributes.put("severites", a); + + // ATTRIBUTE: OS + a = new Attribute("OS"); + a.setParameterName("op_sys"); + optionValues = BugzillaPlugin.getDefault().getProductConfiguration().getOSs(); + for( int i=0; i<optionValues.length; i++ ){ + a.addOptionValue(optionValues[i], optionValues[i]); + } + attributes.put("OSs", a); + + // ATTRIBUTE: Platform + a = new Attribute("Platform"); + a.setParameterName("rep_platform"); + optionValues = BugzillaPlugin.getDefault().getProductConfiguration().getPlatforms(); + for( int i=0; i<optionValues.length; i++ ){ + a.addOptionValue(optionValues[i], optionValues[i]); + } + attributes.put("platforms",a); + + // ATTRIBUTE: Version + a = new Attribute("Version"); + a.setParameterName("version"); + optionValues = BugzillaPlugin.getDefault().getProductConfiguration().getVersions(model.getProduct()); + for( int i=0; i<optionValues.length; i++ ){ + a.addOptionValue(optionValues[i], optionValues[i]); + } + attributes.put("versions", a); + + // ATTRIBUTE: Component + a = new Attribute("Component"); + a.setParameterName("component"); + optionValues = BugzillaPlugin.getDefault().getProductConfiguration().getComponents(model.getProduct()); + for( int i=0; i<optionValues.length; i++ ){ + a.addOptionValue(optionValues[i], optionValues[i]); + } + attributes.put("components", a); + + // ATTRIBUTE: Priority + a = new Attribute("Priority"); + a.setParameterName("bug_severity"); + optionValues = BugzillaPlugin.getDefault().getProductConfiguration().getPriorities(); + for( int i=0; i<optionValues.length; i++ ){ + a.addOptionValue(optionValues[i], optionValues[i]); + } + + // set NBM Attributes (after all Attributes have been created, and added to attributes map) + model.attributes = attributes; + } + + +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/Comment.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/Comment.java new file mode 100644 index 000000000..c16c5df9b --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/Comment.java @@ -0,0 +1,165 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.core; + +import java.io.Serializable; +import java.util.Date; + +/** + * A comment posted on a bug. + */ +public class Comment implements Serializable +{ + /** + * Comment for <code>serialVersionUID</code> + */ + private static final long serialVersionUID = 3978422529214199344L; + + /** Comment's bug */ + private final BugReport bug; + + /** Comment's number */ + private final int number; + + /** Comment's text */ + private String text; + + /** Comment's author */ + private final String author; + + /** Author's realname, if known */ + private final String authorName; + + /** Comment's creation timestamp */ + private final Date created; + + /** Preceding comment */ + private Comment previous; + + /** Following comment */ + private Comment next; + + /** + * Constructor + * @param bug The bug that this comment is associated with + * @param date The date taht this comment was entered on + * @param author The author of the bug + * @param authorName The authors real name + */ + public Comment(BugReport bug, int number, Date date, String author, String authorName) + { + this.bug = bug; + this.number = number; + this.created = date; + this.author = author; + this.authorName = authorName; + } + + /** + * Get the bug that this comment is associated with + * @return The bug that this comment is associated with + */ + public BugReport getBug() + { + return bug; + } + + /** + * Get this comment's number + * @return This comment's number + */ + public int getNumber() + { + return number; + } + + /** + * Get the time that this comment was created + * @return The comments creation timestamp + */ + public Date getCreated() + { + return created; + } + + /** + * Get the author of the comment + * @return The comments author + */ + public String getAuthor() + { + return author; + } + + /** + * Get the authors real name + * @return Returns author's name, or <code>null</code> if not known + */ + public String getAuthorName() + { + return authorName; + } + + /** + * Get the text contained in the comment + * @return The comments text + */ + public String getText() + { + return text; + } + + /** + * Set the comments text + * @param text The text to set the comment to have + */ + public void setText(String text) + { + this.text = text; + } + + /** + * Get the next comment for the bug + * @return Returns the following comment, or <code>null</code> if the last one. + */ + public Comment getNext() + { + return next; + } + + /** + * Set the next comment for the bug + * @param next The comment that is after this one + */ + protected void setNext(Comment next) + { + this.next = next; + } + + /** + * Get the previous comment + * @return Returns preceding comment, or <code>null</code> if the first one + */ + public Comment getPrevious() + { + return previous; + } + + /** + * Seth the previous comment for the bug + * @param previous The comment that is before this one + */ + protected void setPrevious(Comment previous) + { + this.previous = previous; + } +} + diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/IBugzillaBug.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/IBugzillaBug.java new file mode 100644 index 000000000..945dc49b4 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/IBugzillaBug.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.core; + +import java.io.Serializable; +import java.util.List; + +import org.eclipse.ui.IWorkbenchPage; + + +/** + * Interface representing a Bugzilla bug report. + */ +public interface IBugzillaBug extends Serializable { + + /** + * @return bug's id. + */ + public int getId(); + + /** + * @return the server for this bug. + */ + public String getServer(); + + /** + * @return the title label for this bug. + */ + public String getLabel(); + + /** + * @return bug's description. + */ + public String getDescription(); + + /** + * Sets the bug's description. + * @param newDescription + */ + public void setDescription(String newDescription); + + /** + * @return bug's summary. + */ + public String getSummary(); + + /** + * Sets the bug's summary. + * @param newSummary + */ + public void setSummary(String newSummary); + + /** + * Get an attribute given its key + * @return The value of the attribute or <code>null</code> if not present + */ + public Attribute getAttribute(String key); + + /** + * @return the attributes for this bug. + */ + public List<Attribute> getAttributes(); + + /** + * @return <code>true</code> if this bug report is saved offline. + */ + public boolean isSavedOffline(); + + /** + * @return <code>true</code> if this bug was created locally, and does not + * yet exist on a bugzilla server. + */ + public boolean isLocallyCreated(); + + /** + * Sets whether or not this bug is saved offline. + * @param newOfflineState <code>true</code> if this bug is saved offline + */ + public void setOfflineState(boolean newOfflineState); + + /** + * Closes any open editors for this bug. + * @param page The workbench page. This cannot be <code>null</code>. + */ + public void closeEditor(IWorkbenchPage page); + +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/Operation.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/Operation.java new file mode 100644 index 000000000..fa219be17 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/Operation.java @@ -0,0 +1,178 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.core; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A representation of an operation that can be done to the bug when it is submitted + * @author sminto + */ +public class Operation implements Serializable +{ + /** + * Comment for <code>serialVersionUID</code> + */ + private static final long serialVersionUID = 3256442508174045236L; + + /** The name of the value for the knob attribute */ + private String knob_name; + + /** The name of the option that can be chosen */ + private String optionName; + + /** List of option names */ + private List<String> optionNames; + + /** Map of options and their names */ + private Map<String, String> options; + + /** Flag for if we have any options or not */ + private boolean hasOptions = false; + + /** The name of the operation (text that we display) */ + private String op_name; + + /** The option that is selected */ + private String op_sel; + + /** Whether this is to be checked or not */ + private boolean isChecked = false; + + /** + * Constructor + * @param knobName The name of the value for the knob attribute + * @param operationName The display text for the operation + */ + public Operation(String knobName, String operationName) + { + knob_name = knobName; + op_name = operationName; + } + + /** + * Get the knob name + * @return The knob name + */ + public String getKnobName() + { + return knob_name; + } + + /** + * Get the display name + * @return The display name + */ + public String getOperationName() + { + return op_name; + } + + /** + * Check if this has any options + * @return True if there are option values + */ + public boolean hasOptions() + { + return hasOptions; + } + + /** + * Set up this operation to have options + * @param optionName The name for the option attribute + */ + public void setUpOptions(String optionName) + { + hasOptions = true; + this.optionName = optionName; + options = new HashMap<String, String>(); + optionNames = new ArrayList<String>(); + } + + /** + * Add an option value to the operation + * @param name The name of the option + * @param value The value of the option + */ + public void addOption(String name, String value) + { + options.put(name, value); + if(options.size() == 1) + op_sel = name; + optionNames.add(name); + } + + /** + * Get the list of option names for this operation + * @return The list of option names + */ + public List<String> getOptionNames() + { + return optionNames; + } + + /** + * Get the selected option + * @return The selected option name + */ + public String getOptionSelection() { + return op_sel; + } + + + /** + * Set the selected option + * @param string The name of the selected option + */ + public void setOptionSelection(String string) { + op_sel = string; + } + + /** + * Check if this is to be checked or not + * @return True if this is to be checked at the start + */ + public boolean isChecked() { + return isChecked; + } + + /** + * Set whether this option is to be checked or not + * @param b True if it is to be checked + */ + public void setChecked(boolean b) { + isChecked = b; + } + + /** + * Get the name for the option attribute + * @return The option name + */ + public String getOptionName() + { + return optionName; + } + + /** + * Get the value for an option from its name + * @param option The name of the option + * @return The value of the option + */ + public String getOptionValue(String option) + { + return options.get(option); + } + +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/BugParser.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/BugParser.java new file mode 100644 index 000000000..ea82dc7f1 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/BugParser.java @@ -0,0 +1,839 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.core.internal; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.URL; +import java.net.URLEncoder; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import javax.security.auth.login.LoginException; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.mylar.bugzilla.BugzillaPlugin; +import org.eclipse.mylar.bugzilla.IBugzillaConstants; +import org.eclipse.mylar.bugzilla.core.Attribute; +import org.eclipse.mylar.bugzilla.core.BugReport; +import org.eclipse.mylar.bugzilla.core.BugzillaRepository; +import org.eclipse.mylar.bugzilla.core.Comment; +import org.eclipse.mylar.bugzilla.core.Operation; +import org.eclipse.mylar.bugzilla.core.internal.HtmlStreamTokenizer.Token; + + +/** + * @author Shawn Minto + * + * This class parses bugs so that they can be displayed using the bug editor + */ +public class BugParser +{ + /** Parser for dates in the report */ + private static SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + + private static final String keywordsUrl = "describekeywords.cgi"; + + /** + * Parse the case where we have found an attribute name + * @param in The input stream for the bug + * @return The name of the attribute that we are parsing + * @throws IOException + */ + private static String parseAttributeName(HtmlStreamTokenizer tokenizer) + throws IOException, ParseException { + StringBuffer sb = new StringBuffer(); + + parseTableCell(tokenizer, sb); + HtmlStreamTokenizer.unescape(sb); + // remove the colon if there is one + if (sb.charAt(sb.length() - 1) == ':') { + sb.deleteCharAt(sb.length() - 1); + } + return sb.toString(); + } + + /** + * Parse the case where we have found attribute values + * @param in The input stream of the bug + * @param bug The bug report for the current bug + * @param attribute The name of the attribute + * @throws IOException + */ + private static void parseAttributeValue( + BugReport bug, + String attributeName, HtmlStreamTokenizer tokenizer, + String userName, String password) + throws IOException, ParseException { + + Token token = tokenizer.nextToken(); + if (token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag) token.getValue(); + + // make sure that we are on a tag that we care about, not a label + // fix added so that we can parse the mozilla bug pages + if(tag.getTagType() == HtmlTag.Type.LABEL) + { + token = tokenizer.nextToken(); + if (token.getType() == Token.TAG) + tag = (HtmlTag) token.getValue(); + else + { + StringBuffer sb = new StringBuffer(); + if (token.getType() == Token.TEXT) { + sb.append((StringBuffer) token.getValue()); + parseAttributeValueCell(bug, attributeName, tokenizer, sb); + } + } + } + + if (tag.getTagType() == HtmlTag.Type.SELECT && !tag.isEndTag()) { + String parameterName = tag.getAttribute("name"); + parseSelect(bug, attributeName, parameterName, tokenizer); + } + else if (tag.getTagType() == HtmlTag.Type.INPUT && !tag.isEndTag()) { + parseInput(bug, attributeName, tag, userName, password); + } + else if (!tag.isEndTag() || attributeName.equalsIgnoreCase("resolution")) { + if(tag.isEndTag() && attributeName.equalsIgnoreCase("resolution")) + { + Attribute a = new Attribute(attributeName); + a.setValue(""); + bug.addAttribute(a); + } + parseAttributeValueCell(bug, attributeName, tokenizer); + } + } + else { + StringBuffer sb = new StringBuffer(); + if (token.getType() == Token.TEXT) { + sb.append((StringBuffer) token.getValue()); + parseAttributeValueCell(bug, attributeName, tokenizer, sb); + } + } + } + + /** + * Parse the case where the attribute value is just text in a table cell + * @param in The input stream of the bug + * @param bug The bug report for the current bug + * @param attributeName The name of the attribute that we are parsing + * @throws IOException + */ + private static void parseAttributeValueCell( + BugReport bug, + String attributeName, + HtmlStreamTokenizer tokenizer) + throws IOException, ParseException { + StringBuffer sb = new StringBuffer(); + + parseAttributeValueCell(bug, attributeName, tokenizer, sb); + } + + private static void parseAttributeValueCell( + BugReport bug, + String attributeName, + HtmlStreamTokenizer tokenizer, + StringBuffer sb) + throws IOException, ParseException { + + parseTableCell(tokenizer, sb); + HtmlStreamTokenizer.unescape(sb); + + // create a new attribute and set its value to the value that we retrieved + Attribute a = new Attribute(attributeName); + a.setValue(sb.toString()); + + // if we found an attachment attribute, forget about it, else add the + // attribute to the bug report + if (attributeName.toLowerCase().startsWith("attachments")) { + // do nothing + } + else { + if(attributeName.equals("Bug#")) + a.setValue(a.getValue().replaceFirst("alias:", "")); + bug.addAttribute(a); + } + } + + /** + * Reads text into a StringBuffer until it encounters a close table cell tag (</TD>) or start of another cell. + * The text is appended to the existing value of the buffer. <b>NOTE:</b> Does not handle nested cells! + * @param tokenizer + * @param sb + * @throws IOException + * @throws ParseException + */ + private static void parseTableCell(HtmlStreamTokenizer tokenizer, StringBuffer sb) + throws IOException, ParseException { + boolean noWhitespace = false; + for (Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) { + if (token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.TD) { + if (!tag.isEndTag()) { + tokenizer.pushback(token); + } + break; + } + noWhitespace = token.getWhitespace().length() == 0; + } + else if (token.getType() == Token.TEXT) { + // if there was no whitespace between the tag and the + // preceding text, don't insert whitespace before this text + // unless it is there in the source + if (!noWhitespace && token.getWhitespace().length() > 0 && sb.length() > 0) { + sb.append(' '); + } + sb.append((StringBuffer)token.getValue()); + } + } + } + + /** + * Parse the case where the attribute value is an option + * @param in The input stream for the bug + * @param bug The bug report for the current bug + * @param attribute The name of the attribute that we are parsing + * @param parameterName the SELECT tag's name + * @throws IOException + */ + private static void parseSelect( + BugReport bug, + String attributeName, + String parameterName, + HtmlStreamTokenizer tokenizer) + throws IOException, ParseException { + + boolean first = false; + Attribute a = new Attribute(attributeName); + a.setParameterName(parameterName); + + Token token = tokenizer.nextToken(); + while ( token.getType() != Token.EOF) { + if (token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.SELECT && tag.isEndTag()) break; + if (tag.getTagType() == HtmlTag.Type.OPTION && !tag.isEndTag()) { + String optionName = tag.getAttribute("value"); + boolean selected = tag.hasAttribute("selected"); + StringBuffer optionText = new StringBuffer(); + for (token = tokenizer.nextToken(); token.getType() == Token.TEXT; token = tokenizer.nextToken()) { + if (optionText.length() > 0) { + optionText.append(' '); + } + optionText.append((StringBuffer) token.getValue()); + } + a.addOptionValue(optionText.toString(), optionName); + if (selected || first) { + a.setValue(optionText.toString()); + first = false; + } + } + else { + token = tokenizer.nextToken(); + } + } + else { + token = tokenizer.nextToken(); + } + } + + // if we parsed the cc field add the e-mails to the bug report else add the attribute to the bug report + if (attributeName.toLowerCase().startsWith("cc")) { + for (Iterator<String> it = a.getOptionValues().keySet().iterator(); it.hasNext(); ) { + String email = it.next(); + bug.addCC(email); + } + } + else { + bug.addAttribute(a); + } + } + + /** + * Parse the case where the attribute value is an input + * @param bug The bug report for the current bug + * @param attributeName The name of the attribute + * @param tag The INPUT tag + * @throws IOException + */ + private static void parseInput( + BugReport bug, + String attributeName, + HtmlTag tag, String userName, String password) + throws IOException { + + Attribute a = new Attribute(attributeName); + a.setParameterName(tag.getAttribute("name")); + String value = tag.getAttribute("value"); + if (value == null) value = ""; + + // if we found the summary, add it to the bug report + if (attributeName.equalsIgnoreCase("summary")) { + bug.setSummary(value); + } + else if (attributeName.equalsIgnoreCase("Attachments")) { + // do nothing - not a problem after 2.14 + } + else if (attributeName.equalsIgnoreCase("add cc")) { + // do nothing + } + else if (attributeName.toLowerCase().startsWith("cc")) { + // do nothing cc's are options not inputs + } + else { + // otherwise just add the attribute + a.setValue(value); + bug.addAttribute(a); + + if (attributeName.equalsIgnoreCase("keywords") && BugzillaRepository.getURL() != null) { + + BufferedReader input = null; + try { + + String urlText = ""; + + // if we have a user name, may as well log in just in case it is required + if(userName != null && !userName.equals("") && password != null && !password.equals("")) + { + /* + * The UnsupportedEncodingException exception for + * URLEncoder.encode() should not be thrown, since every + * implementation of the Java platform is required to support + * the standard charset "UTF-8" + */ + urlText += "?GoAheadAndLogIn=1&Bugzilla_login=" + URLEncoder.encode(userName, "UTF-8") + "&Bugzilla_password=" + URLEncoder.encode(password, "UTF-8"); + } + + // connect to the bugzilla server to get the keyword list + input = new BufferedReader(new InputStreamReader(new URL(BugzillaRepository.getURL() + "/" + keywordsUrl+urlText).openStream())); + + // parse the valid keywords and add them to the bug + List<String> keywords = new KeywordParser(input).getKeywords(); + bug.setKeywords(keywords); + + } catch(Exception e) { + // throw an exception if there is a problem reading the bug from the server + throw new IOException("Exception while fetching the list of keywords from the server: " + e.getMessage()); + } + finally + { + try{ + if(input != null) + input.close(); + }catch(IOException e) + { + BugzillaPlugin.log(new Status(IStatus.ERROR, IBugzillaConstants.PLUGIN_ID,IStatus.ERROR,"Problem closing the stream", e)); + } + } + } + } + } + + /** + * Parse the case where we are dealing with the description + * @param bug The bug report for the bug + * @throws IOException + */ + private static void parseDescription(BugReport bug, HtmlStreamTokenizer tokenizer) + throws IOException, ParseException { + + StringBuffer sb = new StringBuffer(); + for (Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) { + if (token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.PRE && tag.isEndTag()) break; + } + else if (token.getType() == Token.TEXT) { + if (sb.length() > 0) { + sb.append(token.getWhitespace()); + } + sb.append((StringBuffer) token.getValue()); + } + } + + // set the bug to have the description we retrieved + String text = HtmlStreamTokenizer.unescape(sb).toString(); + bug.setDescription(text); + } + + /** + * Parse the case where we have found the start of a comment + * @param in The input stream of the bug + * @param bug The bug report for the current bug + * @return The comment that we have created with the information + * @throws IOException + * @throws ParseException + */ + private static Comment parseCommentHead(BugReport bug, HtmlStreamTokenizer tokenizer) + throws IOException, ParseException { + int number = 0; + Date date = null; + String author = null; + String authorName = null; + + // get the comment's number + for (Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) { + if (token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.A) { + String href = tag.getAttribute("href"); + if (href != null) { + int index = href.toLowerCase().indexOf("#c"); + if (index == -1) continue; + token = tokenizer.nextToken(); + number = Integer.parseInt(((StringBuffer)token.getValue()).toString().substring(1)); + break; + } + } + } + } + + for (Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) { + if (token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.A) { + String href = tag.getAttribute("href"); + if (href != null) { + int index = href.toLowerCase().indexOf("mailto"); + if (index == -1) continue; + author = href.substring(index + 7); + break; + } + } + } + } + + // get the author's real name + StringBuffer sb = new StringBuffer(); + for (Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) { + if (token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.A && tag.isEndTag()) break; + } + else if (token.getType() == Token.TEXT) { + if (sb.length() > 0) { + sb.append(' '); + } + sb.append((StringBuffer)token.getValue()); + } + } + authorName = sb.toString(); + + // get the comment's date + sb.setLength(0); + for (Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) { + if (token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.I && tag.isEndTag()) break; + } + else if (token.getType() == Token.TEXT) { + if (sb.length() > 0) { + sb.append(' '); + } + sb.append((StringBuffer)token.getValue()); + } + } + date = df.parse(sb.substring(0, 16)); + + return new Comment(bug, number, date, author, authorName); + } + + /** + * Parse the case where we have comment text + * @param in The input stream for the bug + * @param bug The bug report for the current bug + * @param comment The comment to add the text to + * @throws IOException + */ + private static void parseCommentText( + BugReport bug, + Comment comment, HtmlStreamTokenizer tokenizer) + throws IOException, ParseException { + + StringBuffer sb = new StringBuffer(); + for (Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) { + if (token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (sb.length() > 0) { // added to ensure whitespace is not lost if adding a tag within a tag + sb.append(token.getWhitespace()); + } + if (tag.getTagType() == HtmlTag.Type.PRE && tag.isEndTag()) break; + } + else if (token.getType() == Token.TEXT) { + if (sb.length() > 0) { + sb.append(token.getWhitespace()); + } + sb.append((StringBuffer) token.getValue()); + } + } + + HtmlStreamTokenizer.unescape(sb); + comment.setText(sb.toString()); + bug.addComment(comment); + } + + /** + * Parse the full html version of the bug + * @param in - the input stream for the bug + * @param id - the id of the bug that is to be parsed + * @return A bug report for the bug that was parsed + * @throws IOException + * @throws ParseException + */ + public static BugReport parseBug(Reader in, int id, String serverName, boolean is218, String userName, String password) throws IOException, ParseException, LoginException + { + // create a new bug report and set the parser state to the start state + BugReport bug = new BugReport(id, serverName); + ParserState state = ParserState.START; + Comment comment = null; + String attribute = null; + + HtmlStreamTokenizer tokenizer = new HtmlStreamTokenizer(in, null); + + boolean isTitle = false; + boolean possibleBadLogin = false; + boolean checkBody = false; + String title = ""; + StringBuffer body = new StringBuffer(); + + for (Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) { + + // make sure that bugzilla doesn't want us to login + if(token.getType() == Token.TAG && ((HtmlTag)(token.getValue())).getTagType() == HtmlTag.Type.TITLE && !((HtmlTag)(token.getValue())).isEndTag()) + { + isTitle = true; + continue; + } + + if(isTitle) + { + // get all of the data in the title tag + if(token.getType() != Token.TAG) + { + title += ((StringBuffer)token.getValue()).toString().toLowerCase() + " "; + continue; + } + else if(token.getType() == Token.TAG && ((HtmlTag)token.getValue()).getTagType() == HtmlTag.Type.TITLE && ((HtmlTag)token.getValue()).isEndTag()) + { + // check and see if the title seems as though we have wrong login info + if(title.indexOf("login") != -1 || (title.indexOf("invalid") != -1 && title.indexOf("password") != -1) || title.indexOf("check e-mail") != -1) + possibleBadLogin = true; // we possibly have a bad login + + // if the title starts with error, we may have a login problem, or + // there is a problem with the bug (doesn't exist), so we must do + // some more checks + if(title.startsWith("error")) + checkBody = true; + + isTitle = false; + title = ""; + } + continue; + } + + // if we have to add all of the text so that we can check it later + // for problems with the username and password + if(checkBody && token.getType() == Token.TEXT) + { + body.append((StringBuffer)token.getValue()); + body.append(" "); + } + + // we have found the start of an attribute name + if ((state == ParserState.ATT_NAME || state == ParserState.START) && token.getType() == Token.TAG ) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.TD && "right".equalsIgnoreCase(tag.getAttribute("align"))) { + // parse the attribute's name + attribute = parseAttributeName(tokenizer); + + if (attribute.toLowerCase().startsWith("opened")) { + // find the colon so we can get the date + int index = attribute.toLowerCase().indexOf(":"); + String date; + if (index != -1) + date = attribute.substring(index + 1).trim(); + else + date = attribute.substring(6).trim(); + + + // set the bugs opened date to be the date we parsed + bug.setCreated(df.parse(date)); + state = ParserState.ATT_NAME; + continue; + } + + // in 2.18, the last modified looks like the opened so we need to parse it differently + if (attribute.toLowerCase().startsWith("last modified")&& is218) { + // find the colon so we can get the date + int index = attribute.toLowerCase().indexOf(":"); + String date; + if (index != -1) + date = attribute.substring(index + 1).trim(); + else + date = attribute.substring(6).trim(); + + // create a new attribute and set the date + Attribute t = new Attribute("Last Modified"); + t.setValue(date); + + // add the attribute to the bug report + bug.addAttribute(t); + state = ParserState.ATT_NAME; + continue; + } + + state = ParserState.ATT_VALUE; + continue; + } + else if (tag.getTagType() == HtmlTag.Type.INPUT && "radio".equalsIgnoreCase(tag.getAttribute("type")) && "knob".equalsIgnoreCase(tag.getAttribute("name"))) + { + // we found a radio button + parseOperations(bug, tokenizer, tag, is218); + } + } + + // we have found the start of attribute values + if (state == ParserState.ATT_VALUE && token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.TD) { + // parse the attribute values + parseAttributeValue(bug, attribute, tokenizer, userName, password); + + state = ParserState.ATT_NAME; + attribute = null; + continue; + } + } + + // we have found the start of a comment + if (state == ParserState.DESC_START && token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.I) { + // parse the comment's start + comment = parseCommentHead(bug, tokenizer); + + state = ParserState.DESC_VALUE; + continue; + } + } + + // we have found the start of the comment text + if (state == ParserState.DESC_VALUE && token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.PRE) { + // parse the text of the comment + parseCommentText(bug, comment, tokenizer); + + comment = null; + state = ParserState.DESC_START; + continue; + } + } + + // we have found the description of the bug + if ((state == ParserState.ATT_NAME || state == ParserState.START) && + token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.PRE) { + // parse the description for the bug + parseDescription(bug, tokenizer); + + state = ParserState.DESC_START; + continue; + } + } + + //parse hidden fields + if((state == ParserState.ATT_NAME || state == ParserState.START) && token.getType() == Token.TAG) + { + HtmlTag tag = (HtmlTag)token.getValue(); + if(tag.getTagType() == HtmlTag.Type.INPUT && tag.getAttribute("type") != null &&"hidden".equalsIgnoreCase(tag.getAttribute("type").trim())) + { + + Attribute a = new Attribute(tag.getAttribute("name")); + a.setParameterName(tag.getAttribute("name")); + a.setValue(tag.getAttribute("value")); + a.setHidden(true); + bug.addAttribute(a); + continue; + } + } + } + + + + // if we are to check the body, make sure that there wasn't a bad login + if(checkBody) + { + String b = body.toString(); + if(b.indexOf("login") != -1 || ((b.indexOf("invalid") != -1 || b.indexOf("not valid") != -1) && b.indexOf("password") != -1) || b.indexOf("check e-mail") != -1) + possibleBadLogin = true; // we possibly have a bad login + } + + // fixed bug 59 + // if there is no summary or created date, we expect that + // the bug doesn't exist, so set it to null + + // if the bug seems like it doesn't exist, and we suspect a login problem, assume that there was a login problem + if(bug.getCreated() == null && bug.getAttributes().isEmpty()) { + if (possibleBadLogin) { + throw new LoginException("Bugzilla login information incorrect"); + } + else { + return null; + } + } + // we are done...return the bug + return bug; + } + + /** + * Parse the operations that are allowed on the bug (Assign, Re-open, fix) + * @param bug The bug to add the operations to + * @param tokenizer The stream tokenizer for the bug + * @param tag The last tag that we were on + */ + private static void parseOperations(BugReport bug, HtmlStreamTokenizer tokenizer, HtmlTag tag, boolean is218) throws ParseException, IOException { + + String knobName = tag.getAttribute("value"); + boolean isChecked = false; + if(tag.getAttribute("checked") != null && tag.getAttribute("checked").equals("checked")) + isChecked = true; + StringBuffer sb = new StringBuffer(); + + Token lastTag = null; + + for (Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) { + if(token.getType() == Token.TAG) + { + tag = (HtmlTag)token.getValue(); + + if(!(tag.getTagType() == HtmlTag.Type.A || tag.getTagType() == HtmlTag.Type.B || tag.getTagType() == HtmlTag.Type.STRONG || tag.getTagType() == HtmlTag.Type.LABEL)) + { + lastTag = token; + break; + } + else{ + + if(is218 && tag.getTagType() == HtmlTag.Type.LABEL){ + continue; + } + else if(tag.getTagType() == HtmlTag.Type.A || tag.getTagType() == HtmlTag.Type.B || tag.getTagType() == HtmlTag.Type.STRONG){ + sb.append(tag.toString().trim() + " "); + } else { + break; + } + } + } + else if(token.getType() == Token.TEXT && !token.toString().trim().equals("\n")) + sb.append(token.toString().trim() + " "); + } + + String displayName = HtmlStreamTokenizer.unescape(sb).toString(); + Operation o = new Operation(knobName, displayName); + o.setChecked(isChecked); + + if(lastTag != null) + { + tag = (HtmlTag)lastTag.getValue(); + if(tag.getTagType() != HtmlTag.Type.SELECT) + { + tokenizer.pushback(lastTag); + if(tag.getTagType() == HtmlTag.Type.INPUT && !("radio".equalsIgnoreCase(tag.getAttribute("type")) && "knob".equalsIgnoreCase(tag.getAttribute("name")))) + { + return; + } + } + else + { + Token token = tokenizer.nextToken(); + // parse the options + + tag = (HtmlTag)token.getValue(); + o.setUpOptions(((HtmlTag)lastTag.getValue()).getAttribute("name")); + + + while ( token.getType() != Token.EOF) { + if (token.getType() == Token.TAG) { + tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.SELECT && tag.isEndTag()) break; + if (tag.getTagType() == HtmlTag.Type.OPTION && !tag.isEndTag()) { + String optionName = tag.getAttribute("value"); + StringBuffer optionText = new StringBuffer(); + for (token = tokenizer.nextToken(); token.getType() == Token.TEXT; token = tokenizer.nextToken()) { + if (optionText.length() > 0) { + optionText.append(' '); + } + optionText.append((StringBuffer) token.getValue()); + } + o.addOption(optionText.toString(), optionName); + } + else { + token = tokenizer.nextToken(); + } + } + else { + token = tokenizer.nextToken(); + } + } + } + } + + bug.addOperation(o); + } + + /** + * Enum class for describing current state of Bugzilla report parser. + */ + private static class ParserState + { + /** An instance of the start state */ + protected static final ParserState START = new ParserState("start"); + + /** An instance of the state when the parser found an attribute name */ + protected static final ParserState ATT_NAME = new ParserState("att_name"); + + /** An instance of the state when the parser found an attribute value */ + protected static final ParserState ATT_VALUE = new ParserState("att_value"); + + /** An instance of the state when the parser found a description */ + protected static final ParserState DESC_START = new ParserState("desc_start"); + + /** An instance of the state when the parser found a description value */ + protected static final ParserState DESC_VALUE = new ParserState("desc_value"); + + /** State's human-readable name */ + private String name; + + /** + * Constructor + * @param description - The states human readable name + */ + private ParserState(String description) + { + this.name = description; + } + + @Override + public String toString() + { + return name; + } + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/HtmlStreamTokenizer.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/HtmlStreamTokenizer.java new file mode 100644 index 000000000..e0a1e32b0 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/HtmlStreamTokenizer.java @@ -0,0 +1,633 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.core.internal; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.net.URL; +import java.text.ParseException; +import java.util.HashMap; + +public class HtmlStreamTokenizer { + + /** parser state */ + private State state; + /** reader from which to parse the text */ + private BufferedReader in; + /** base URL for resolving relative URLs */ + private URL base; + /** buffer holding the text of the current token */ + private StringBuffer textBuffer; + /** buffer holding whitespace preceding the current token */ + private StringBuffer whitespaceBuffer; + /** holds a token that was read and then put back in the queue to be returned again on <code>nextToken</code> call */ + private Token pushbackToken; + /** holds a character that was read and then determined not to be part of the current token */ + private int pushbackChar; + /** current quote delimiter (single or double) */ + private int quoteChar; + + /** + * Constructor. + * @param in reader for the HTML document to tokenize + * @param base URL for resolving relative URLs + */ + public HtmlStreamTokenizer(Reader in, URL base) { + textBuffer = new StringBuffer(); + whitespaceBuffer = new StringBuffer(); + pushbackChar = 0; + state = State.TEXT; + this.in = new BufferedReader(in); + this.base = base; + } + + /** + * Returns the next token from the stream. + */ + public Token nextToken() throws IOException, ParseException { + if (pushbackToken != null) { + Token token = pushbackToken; + pushbackToken = null; + return token; + } + + int closingComment = 0; + + textBuffer.setLength(0); + whitespaceBuffer.setLength(0); + do { + int ch; + if (pushbackChar != 0) { + ch = pushbackChar; + pushbackChar = 0; + } + else { + ch = in.read(); + } + if (ch < 0) { + State oldState = state; + state = State.EOF; + if (textBuffer.length() > 0 && oldState == State.TEXT) { + return new Token(textBuffer, whitespaceBuffer, false); + } + else { + return new Token(); + } + } + if (state == State.TEXT) { + if (ch == '<') { + state = State.TAG; + if (textBuffer.length() > 0) + return new Token(textBuffer, whitespaceBuffer, false); + } + else if (Character.isWhitespace((char)ch)) { + pushbackChar = ch; + state = State.WS; + if (textBuffer.length() > 0) + return new Token(textBuffer, whitespaceBuffer, false); + } + else { + textBuffer.append((char) ch); + } + } + else if (state == State.WS) { + if (!Character.isWhitespace((char)ch)) { + pushbackChar = ch; + state = State.TEXT; + } + else { + whitespaceBuffer.append((char) ch); + } + } + else if (state == State.TAG) { + if (ch == '>') { + state = State.TEXT; + HtmlTag tag = new HtmlTag(base); + parseTag(textBuffer.toString(), tag); + return new Token(tag, whitespaceBuffer); + } + if (ch == '<' && textBuffer.length() == 0) { + textBuffer.append("<<"); + state = State.TEXT; + } + else if (ch == '-' && textBuffer.length() == 2 && textBuffer.charAt(1) == '-' + && textBuffer.charAt(0) == '!') { + textBuffer.setLength(0); + state = State.COMMENT; + } + else if (ch == '\'' || ch == '"') { + quoteChar = ch; + textBuffer.append((char) ch); + state = State.TAG_QUOTE; + } + else { + textBuffer.append((char) ch); + } + } + else if (state == State.TAG_QUOTE) { + if (ch == '>') { + pushbackChar = ch; + state = State.TAG; + } + else { + textBuffer.append((char) ch); + if (ch == quoteChar) + state = State.TAG; + } + } + else if (state == State.COMMENT) { + if (ch == '>' && closingComment >= 2) { + textBuffer.setLength(textBuffer.length() - 2); + closingComment = 0; + state = State.TEXT; + return new Token(textBuffer, whitespaceBuffer, true); + } + if (ch == '-') { + closingComment++; + } + else { + closingComment = 0; + } + textBuffer.append((char) ch); + } + } while (true); + } + + /** + * Pushes the token back into the queue, to be returned by the subsequent + * call to <code>nextToken</code> + */ + public void pushback(Token token) { + pushbackToken = token; + } + + /** + * Parses an HTML tag out of a string of characters. + */ + private static void parseTag(String s, HtmlTag tag) throws ParseException { + + int i = 0; + for (; i < s.length() && Character.isWhitespace(s.charAt(i)); i++){ + // just move forward + } + if (i == s.length()) + throw new ParseException("parse empty tag", 0); + + int start = i; + for (; i < s.length() && !Character.isWhitespace(s.charAt(i)); i++){ + // just move forward + } + tag.setTagName(s.substring(start, i)); + + for (; i < s.length() && Character.isWhitespace(s.charAt(i)); i++){ + // just move forward + } + if (i == s.length()) { + return; + } + else { + parseAttributes(tag, s, i); + return; + } + } + + /** + * parses HTML tag attributes from a buffer and sets them in an HtmlTag + */ + private static void parseAttributes(HtmlTag tag, String s, int i) throws ParseException { + while (i < s.length()) { + // skip whitespace + while (i < s.length() && Character.isWhitespace(s.charAt(i))) + i++; + + if (i == s.length()) + return; + + // read the attribute name -- the rule might be looser than the RFC specifies: + // everything up to a space or an equal sign is included + int start = i; + for (; i < s.length() && !Character.isWhitespace(s.charAt(i)) && s.charAt(i) != '='; i++){ + // just move forward + } + String attributeName = s.substring(start, i).toLowerCase(); + + for (; i < s.length() && Character.isWhitespace(s.charAt(i)); i++){ + // just move forward + } + if (i == s.length() || s.charAt(i) != '=') { + // no attribute value + tag.setAttribute(attributeName, ""); + continue; + } + + // skip whitespace to the start of attribute value + for (i = i+1; i < s.length() && Character.isWhitespace(s.charAt(i)); i++){ + // just move forward + } + if (i == s.length()) return; + + // read the attribute value -- the rule for unquoted attribute value is + // looser than the one in Conolly's W3C 1996 lexical analyzer draft: everything + // is included up to the next space + String attributeValue; + if (s.charAt(i) == '"') { + start = ++i; + for (; i < s.length() && s.charAt(i) != '"'; i++){ + // just move forward + } + if (i == s.length()) return; // shouldn't happen if input returned by nextToken + attributeValue = unescape(s.substring(start, i)); + i++; + } + else if (s.charAt(i) == '\'') { + start = ++i; + for (; i < s.length() && s.charAt(i) != '\''; i++){ + // just move forward + } + if (i == s.length()) return; // shouldn't happen if input returned by nextToken + attributeValue = unescape(s.substring(start, i)); + i++; + } + else { + start = i; + for (; i < s.length() && !Character.isWhitespace(s.charAt(i)); i++){ + // just move forward + } + attributeValue = s.substring(start, i); + } + tag.setAttribute(attributeName, attributeValue); + } + } + + /** + * Returns a string with HTML escapes changed into their corresponding characters. + */ + public static String unescape(String s) { + if (s.indexOf('&') == -1) { + return s; + } + else { + StringBuffer sb = new StringBuffer(s); + unescape(sb); + return sb.toString(); + } + } + + /** + * Replaces (in-place) HTML escapes in a StringBuffer with their corresponding characters. + */ + public static StringBuffer unescape(StringBuffer sb) { + int i = 0; // index into the unprocessed section of the buffer + int j = 0; // index into the processed section of the buffer + + while (i < sb.length()) { + char ch = sb.charAt(i); + if (ch == '&') { + int start = i; + String escape = null; + for (i = i+1; i < sb.length(); i++) { + ch = sb.charAt(i); + if (!Character.isLetterOrDigit(ch) && !(ch == '#' && i == (start+1))) { + escape = sb.substring(start+1, i); + break; + } + } + if (i == sb.length() && i != (start+1)) { + escape = sb.substring(start + 1); + } + if (escape != null) { + Character character = parseReference(escape); + if (character != null) { + ch = character.charValue(); + } + else { + // not an HTML escape; rewind + i = start; + ch = '&'; + } + } + } + sb.setCharAt(j, ch); + i++; + j++; + } + + sb.setLength(j); + return sb; + } + + /** + * Parses HTML character and entity references and returns the + * corresponding character. + */ + private static Character parseReference(String s) { + if (s.length() == 0) + return null; + + if (s.charAt(0) == '#') { + // character reference + if (s.length() == 1) + return null; + + try { + int value; + if (s.charAt(1) == 'x') { + // Hex reference + value = Integer.parseInt(s.substring(2), 16); + } + else { + // Decimal reference + value = Integer.parseInt(s.substring(1)); + } + return new Character((char)value); + } catch (NumberFormatException e) { + return null; + } + } + else { + return entities.get(s); + } + } + + /** + * Class for current token. + */ + public static class Token { + public static final Type EOF = new Type(); + public static final Type TEXT = new Type(); + public static final Type TAG = new Type(); + public static final Type COMMENT = new Type(); + + /** token's type */ + private Type type; + /** token's value */ + private Object value; + /** whitespace preceding the token */ + private StringBuffer whitespace; + + /** + * Constructor for the EOF token. + */ + protected Token() { + type = EOF; + value = null; + whitespace = null; + } + + /** + * Constructor for the HTML tag tokens. + */ + protected Token(HtmlTag tag, StringBuffer whitespace) { + type = TAG; + value = tag; + this.whitespace = whitespace; + } + + /** + * Constructor for regular text and comments. + */ + protected Token(StringBuffer text, StringBuffer whitespace, boolean comment) { + if (comment) { + type = COMMENT; + } + else { + type = TEXT; + } + this.value = text; + this.whitespace = whitespace; + } + + /** + * Returns the token's type. + */ + public Type getType() { + return type; + } + + /** + * Returns the whitespace preceding the token. + */ + public StringBuffer getWhitespace() { + return whitespace; + } + + /** + * Returns the token's value. This is an HtmlTag for tokens of type <code>TAG</code> + * and a StringBuffer for tokens of type <code>TEXT</code> and <code>COMMENT</code>. + * For tokens of type <code>EOF</code>, the value is <code>null</code>. + */ + public Object getValue() { + return value; + } + + /** + * Returns the string representation of the token, including the preceding whitespace. + */ + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + if (whitespace != null) { + sb.append(whitespace); + } + if (value != null) { + if (type == TAG) { + sb.append('<'); + } + else if (type == COMMENT) { + sb.append("<!"); + } + sb.append(value); + if (type == TAG) { + sb.append('>'); + } + else if (type == COMMENT) { + sb.append("-->"); + } + } + return sb.toString(); + } + + /** + * Private enum class for token type. + */ + private static class Type { + private Type() { + // don't need to do anything + } + } + } + + /** + * Enum class for parser state. + */ + private static class State { + static final State EOF = new State(); + static final State COMMENT = new State(); + static final State TEXT = new State(); + static final State TAG = new State(); + static final State WS = new State(); + static final State TAG_QUOTE = new State(); + + private State() { + // don't need to do anything + } + } + + /** names and values of HTML entity references */ + private static HashMap<String, Character> entities; + + /* + * Based on ISO 8879. + * + * Portions © International Organization for Standardization 1986 Permission + * to copy in any form is granted for use with conforming SGML systems and + * applications as defined in ISO 8879, provided this notice is included in + * all copies. + * + */ + static { + entities = new HashMap<String, Character>(); + entities.put(new String("nbsp"), new Character('\240')); // no-break space = non-breaking space + entities.put(new String("iexcl"), new Character('\241')); // inverted exclamation mark + entities.put(new String("cent"), new Character('\242')); // cent sign + entities.put(new String("pound"), new Character('\243')); // pound sign + entities.put(new String("curren"), new Character('\244')); // currency sign + entities.put(new String("yen"), new Character('\245')); // yen sign = yuan sign + entities.put(new String("brvbar"), new Character('\246')); // broken bar = broken vertical bar + entities.put(new String("sect"), new Character('\247')); // section sign + entities.put(new String("uml"), new Character('\250')); // diaeresis = spacing diaeresis + entities.put(new String("copy"), new Character('\251')); // copyright sign + entities.put(new String("ordf"), new Character('\252')); // feminine ordinal indicator + entities.put(new String("laquo"), new Character('\253')); // left-pointing double angle quotation mark = left pointing guillemet + entities.put(new String("not"), new Character('\254')); // not sign + entities.put(new String("shy"), new Character('\255')); // soft hyphen = discretionary hyphen + entities.put(new String("reg"), new Character('\256')); // registered sign = registered trade mark sign + entities.put(new String("macr"), new Character('\257')); // macron = spacing macron = overline = APL overbar + entities.put(new String("deg"), new Character('\260')); // degree sign + entities.put(new String("plusmn"), new Character('\261')); // plus-minus sign = plus-or-minus sign + entities.put(new String("sup2"), new Character('\262')); // superscript two = superscript digit two = squared + entities.put(new String("sup3"), new Character('\263')); // superscript three = superscript digit three = cubed + entities.put(new String("acute"), new Character('\264')); // acute accent = spacing acute + entities.put(new String("micro"), new Character('\265')); // micro sign + entities.put(new String("para"), new Character('\266')); // pilcrow sign = paragraph sign + entities.put(new String("middot"), new Character('\267')); // middle dot = Georgian comma = Greek middle dot + entities.put(new String("cedil"), new Character('\270')); // cedilla = spacing cedilla + entities.put(new String("sup1"), new Character('\271')); // superscript one = superscript digit one + entities.put(new String("ordm"), new Character('\272')); // masculine ordinal indicator + entities.put(new String("raquo"), new Character('\273')); // right-pointing double angle quotation mark = right pointing guillemet + entities.put(new String("frac14"), new Character('\274')); // vulgar fraction one quarter = fraction one quarter + entities.put(new String("frac12"), new Character('\275')); // vulgar fraction one half = fraction one half + entities.put(new String("frac34"), new Character('\276')); // vulgar fraction three quarters = fraction three quarters + entities.put(new String("iquest"), new Character('\277')); // inverted question mark = turned question mark + entities.put(new String("Agrave"), new Character('\300')); // latin capital letter A with grave = latin capital letter A grave + entities.put(new String("Aacute"), new Character('\301')); // latin capital letter A with acute + entities.put(new String("Acirc"), new Character('\302')); // latin capital letter A with circumflex + entities.put(new String("Atilde"), new Character('\303')); // latin capital letter A with tilde + entities.put(new String("Auml"), new Character('\304')); // latin capital letter A with diaeresis + entities.put(new String("Aring"), new Character('\305')); // latin capital letter A with ring above = latin capital letter A ring + entities.put(new String("AElig"), new Character('\306')); // latin capital letter AE = latin capital ligature AE + entities.put(new String("Ccedil"), new Character('\307')); // latin capital letter C with cedilla + entities.put(new String("Egrave"), new Character('\310')); // latin capital letter E with grave + entities.put(new String("Eacute"), new Character('\311')); // latin capital letter E with acute + entities.put(new String("Ecirc"), new Character('\312')); // latin capital letter E with circumflex + entities.put(new String("Euml"), new Character('\313')); // latin capital letter E with diaeresis + entities.put(new String("Igrave"), new Character('\314')); // latin capital letter I with grave + entities.put(new String("Iacute"), new Character('\315')); // latin capital letter I with acute + entities.put(new String("Icirc"), new Character('\316')); // latin capital letter I with circumflex + entities.put(new String("Iuml"), new Character('\317')); // latin capital letter I with diaeresis + entities.put(new String("ETH"), new Character('\320')); // latin capital letter ETH + entities.put(new String("Ntilde"), new Character('\321')); // latin capital letter N with tilde + entities.put(new String("Ograve"), new Character('\322')); // latin capital letter O with grave + entities.put(new String("Oacute"), new Character('\323')); // latin capital letter O with acute + entities.put(new String("Ocirc"), new Character('\324')); // latin capital letter O with circumflex + entities.put(new String("Otilde"), new Character('\325')); // latin capital letter O with tilde + entities.put(new String("Ouml"), new Character('\326')); // latin capital letter O with diaeresis + entities.put(new String("times"), new Character('\327')); // multiplication sign + entities.put(new String("Oslash"), new Character('\330')); // latin capital letter O with stroke = latin capital letter O slash + entities.put(new String("Ugrave"), new Character('\331')); // latin capital letter U with grave + entities.put(new String("Uacute"), new Character('\332')); // latin capital letter U with acute + entities.put(new String("Ucirc"), new Character('\333')); // latin capital letter U with circumflex + entities.put(new String("Uuml"), new Character('\334')); // latin capital letter U with diaeresis + entities.put(new String("Yacute"), new Character('\335')); // latin capital letter Y with acute + entities.put(new String("THORN"), new Character('\336')); // latin capital letter THORN + entities.put(new String("szlig"), new Character('\337')); // latin small letter sharp s = ess-zed + entities.put(new String("agrave"), new Character('\340')); // latin small letter a with grave = latin small letter a grave + entities.put(new String("aacute"), new Character('\341')); // latin small letter a with acute + entities.put(new String("acirc"), new Character('\342')); // latin small letter a with circumflex + entities.put(new String("atilde"), new Character('\343')); // latin small letter a with tilde + entities.put(new String("auml"), new Character('\344')); // latin small letter a with diaeresis + entities.put(new String("aring"), new Character('\345')); // latin small letter a with ring above = latin small letter a ring + entities.put(new String("aelig"), new Character('\346')); // latin small letter ae = latin small ligature ae + entities.put(new String("ccedil"), new Character('\347')); // latin small letter c with cedilla + entities.put(new String("egrave"), new Character('\350')); // latin small letter e with grave + entities.put(new String("eacute"), new Character('\351')); // latin small letter e with acute + entities.put(new String("ecirc"), new Character('\352')); // latin small letter e with circumflex + entities.put(new String("euml"), new Character('\353')); // latin small letter e with diaeresis + entities.put(new String("igrave"), new Character('\354')); // latin small letter i with grave + entities.put(new String("iacute"), new Character('\355')); // latin small letter i with acute + entities.put(new String("icirc"), new Character('\356')); // latin small letter i with circumflex + entities.put(new String("iuml"), new Character('\357')); // latin small letter i with diaeresis + entities.put(new String("eth"), new Character('\360')); // latin small letter eth + entities.put(new String("ntilde"), new Character('\361')); // latin small letter n with tilde + entities.put(new String("ograve"), new Character('\362')); // latin small letter o with grave + entities.put(new String("oacute"), new Character('\363')); // latin small letter o with acute + entities.put(new String("ocirc"), new Character('\364')); // latin small letter o with circumflex + entities.put(new String("otilde"), new Character('\365')); // latin small letter o with tilde + entities.put(new String("ouml"), new Character('\366')); // latin small letter o with diaeresis + entities.put(new String("divide"), new Character('\367')); // division sign + entities.put(new String("oslash"), new Character('\370')); // latin small letter o with stroke = latin small letter o slash + entities.put(new String("ugrave"), new Character('\371')); // latin small letter u with grave + entities.put(new String("uacute"), new Character('\372')); // latin small letter u with acute + entities.put(new String("ucirc"), new Character('\373')); // latin small letter u with circumflex + entities.put(new String("uuml"), new Character('\374')); // latin small letter u with diaeresis + entities.put(new String("yacute"), new Character('\375')); // latin small letter y with acute + entities.put(new String("thorn"), new Character('\376')); // latin small letter thorn + entities.put(new String("yuml"), new Character('\377')); // latin small letter y with diaeresis + + // Special characters + entities.put(new String("quot"), new Character('\42')); // quotation mark = APL quote + entities.put(new String("amp"), new Character('\46')); // ampersand + entities.put(new String("lt"), new Character('\74')); // less-than sign + entities.put(new String("gt"), new Character('\76')); // greater-than sign + // Latin Extended-A + entities.put(new String("OElig"), new Character('\u0152')); // latin capital ligature OE + entities.put(new String("oelig"), new Character('\u0153')); // latin small ligature oe, ligature is a misnomer, this is a separate character in some languages + entities.put(new String("Scaron"), new Character('\u0160')); // latin capital letter S with caron + entities.put(new String("scaron"), new Character('\u0161')); // latin small letter s with caron + entities.put(new String("Yuml"), new Character('\u0178')); // latin capital letter Y with diaeresis + // Spacing Modifier Letters + entities.put(new String("circ"), new Character('\u02c6')); // modifier letter circumflex accent + entities.put(new String("tilde"), new Character('\u02dc')); // small tilde + // General punctuation + entities.put(new String("ensp"), new Character('\u2002')); // en space + entities.put(new String("emsp"), new Character('\u2003')); // em space + entities.put(new String("thinsp"), new Character('\u2009')); // thin space + entities.put(new String("zwnj"), new Character('\u200c')); // zero width non-joiner + entities.put(new String("zwj"), new Character('\u200d')); // zero width joiner + entities.put(new String("lrm"), new Character('\u200e')); // left-to-right mark + entities.put(new String("rlm"), new Character('\u200f')); // right-to-left mark + entities.put(new String("ndash"), new Character('\u2013')); // en dash + entities.put(new String("mdash"), new Character('\u2014')); // em dash + entities.put(new String("lsquo"), new Character('\u2018')); // left single quotation mark + entities.put(new String("rsquo"), new Character('\u2019')); // right single quotation mark + entities.put(new String("sbquo"), new Character('\u201a')); // single low-9 quotation mark + entities.put(new String("ldquo"), new Character('\u201c')); // left double quotation mark + entities.put(new String("rdquo"), new Character('\u201d')); // right double quotation mark + entities.put(new String("bdquo"), new Character('\u201e')); // double low-9 quotation mark + entities.put(new String("dagger"), new Character('\u2020')); // dagger + entities.put(new String("Dagger"), new Character('\u2021')); // double dagger + entities.put(new String("permil"), new Character('\u2030')); // per mille sign + entities.put(new String("lsaquo"), new Character('\u2039')); // single left-pointing angle quotation mark, not yet standardized + entities.put(new String("rsaquo"), new Character('\u203a')); // single right-pointing angle quotation mark, not yet standardized + entities.put(new String("euro"), new Character('\u20ac')); // euro sign + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/HtmlTag.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/HtmlTag.java new file mode 100644 index 000000000..40e3586ea --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/HtmlTag.java @@ -0,0 +1,340 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.core.internal; + +import java.net.URL; +import java.text.ParseException; +import java.util.HashMap; +import java.util.Iterator; +import javax.swing.text.html.HTML.Tag; + +/** + * Class representing an HTML (3.2) tag and its attributes. + */ +public class HtmlTag { + /** tag's name */ + private String tagName; + /** tag type enum */ + private Tag tagType; + /** true if the tag is a closing tag */ + private boolean isEndTag; + /** tag's attributes (keys are lowercase attribute names) */ + private HashMap<String, String> attributes; + /** tag's base url */ + private URL baseUrl; + + /** + * Basic constructor. The tag is uninitialized. + */ + public HtmlTag() { + tagName = null; + tagType = Type.UNKNOWN; + isEndTag = false; + attributes = new HashMap<String, String>(); + baseUrl = null; + } + + /** + * Copy constructor. + */ + @SuppressWarnings("unchecked") + public HtmlTag(HtmlTag htmltag) { + tagName = null; + tagType = Type.UNKNOWN; + isEndTag = false; + attributes = new HashMap<String, String>(); + tagName = new String(htmltag.tagName); + baseUrl = htmltag.baseUrl; + tagType = htmltag.tagType; + isEndTag = htmltag.isEndTag; + attributes = (HashMap)htmltag.attributes.clone(); + } + + /** + * Constructor. + */ + public HtmlTag(String s) throws ParseException { + attributes = new HashMap<String, String>(); + setTagName(s); + baseUrl = null; + } + + /** + * Constructor creating an otherwise empty tag, but with a given base url. + */ + public HtmlTag(URL url) { + tagName = null; + tagType = Type.UNKNOWN; + isEndTag = false; + attributes = new HashMap<String, String>(); + baseUrl = url; + } + + /** + * Returns the tag's type (linked to the tag's name). + */ + public Tag getTagType() { + return tagType; + } + + /** + * Returns the tag's name (e.g., "HEAD", "P", etc.). + */ + public String getTagName() { + return tagName; + } + + /** + * Sets the tag's name and type, if known. + * + * @throws IllegalArgumentException + * if the argument is <code>null</code> or empty string + */ + public void setTagName(String s) throws IllegalArgumentException { + if (s == null || s.length() == 0) throw new IllegalArgumentException("Empty tag name"); + if (s.charAt(0) == '/') { + isEndTag = true; + s = s.substring(1); + } + if (s.length() == 0) throw new IllegalArgumentException("Empty tag name"); + tagName = s; + tagType = tags.get(s.toUpperCase()); + if (tagType == null) { + tagType = Type.UNKNOWN; + } + } + + /** + * Returns <code>true</code> if the tag is a closing tag. + */ + public boolean isEndTag() { + return isEndTag; + } + + /** + * Returns the value of a tag's attribute as an integer. + */ + public int getIntAttribute(String s) throws NumberFormatException { + return Integer.parseInt(getAttribute(s)); + } + + /** + * Returns the value of a tag's attribute, or NULL if it doesn't exist. + */ + public String getAttribute(String s) { + return attributes.get(s); + } + + /** + * Returns <code>true</code> if the tag contains attribute with the given name. + */ + public boolean hasAttribute(String s) { + return getAttribute(s) != null; + } + + /** + * Sets the value of a tag's attribute. + */ + public void setAttribute(String name, String value) { + attributes.put(name.toLowerCase(), value); + } + + public StringBuffer getURLs() { + StringBuffer sb = new StringBuffer(); + + Iterator<String> attributeNames = attributes.keySet().iterator(); + Iterator<String> attributeValues = attributes.values().iterator(); + while (attributeNames.hasNext()) { + String attributeName = attributeNames.next(); + if (attributeName.compareTo("href") == 0 || attributeName.compareTo("src") == 0) { + String target = attributeValues.next(); + if (!target.endsWith(".jpg") + && !target.endsWith(".gif") + && !target.endsWith(".css") + && !target.endsWith(".js") + && !target.startsWith("mailto") + && target.lastIndexOf("#") == -1 + && target.length() > 0) { + + for (int i = 0; i < target.length(); i++) { + char ch = target.charAt(i); + if (!Character.isWhitespace(ch)) { + if (i > 0) target = target.substring(i+1); + break; + } + } + target = target.replace('\\', '/'); + + if (target.startsWith("news:") || (target.indexOf("://") != -1 && target.length() >= 7)) { + // Absolute URL + if (target.substring(0, 7).compareToIgnoreCase("http://") == 0) + sb.append(target); + } + else { + // Relative URL + + String baseDir = baseUrl.getPath(); + int lastSep = -1; + for (int i = 0; i < baseDir.length(); i++) { + char ch = baseDir.charAt(i); + if (ch == '/') lastSep = i; + else if (ch == '?') break; + } + if (lastSep >= 0) baseDir = baseDir.substring(0, lastSep); + while (baseDir.length() > 1 && baseDir.endsWith("/.")) { + baseDir = baseDir.substring(0, baseDir.length()-2); + } + + if (target.startsWith("//")) { + sb.append(baseUrl.getProtocol() + ":" + target); + } + else if (target.startsWith("/")) { + sb.append(baseUrl.getProtocol() + "://" + baseUrl.getHost() + target); + } + else { + while (target.startsWith("../")) { + if (baseDir.length() > 0) { + // can't go above root + baseDir = baseDir.substring(0, baseDir.lastIndexOf("/")); + } + target = target.substring(3); + } + sb.append(baseUrl.getProtocol() + "://" + baseUrl.getHost() + baseDir + "/" + target); + } + } + } + } + else { + attributeValues.next(); + } + } + + return sb; + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append('<'); + if (isEndTag) + sb.append('/'); + sb.append(tagName); + Iterator<String> keys = attributes.keySet().iterator(); + Iterator<String> values = attributes.values().iterator(); + while (keys.hasNext()) { + String name = keys.next(); + sb.append(' '); + sb.append(name); + String value = values.next(); + if (value.length() > 0) { + sb.append("=\""); + sb.append(value); + sb.append('"'); + } + } + sb.append('>'); + + return sb.toString(); + } + + /** + * Enum class for tag types. + */ + public static class Type extends Tag { + public static final Tag UNKNOWN = new Tag(); + public static final Tag THEAD = new Type("THEAD"); + public static final Tag DOCTYPE = new Type("!DOCTYPE"); + public static final Tag LABEL = new Type("LABEL"); + + private Type(String name) { + super(name); + } + } + + private static HashMap<String, Tag> tags; + static { + tags = new HashMap<String, Tag>(); + tags.put(new String("A"), Tag.A); + tags.put(new String("ADDRESS"), Tag.ADDRESS); + tags.put(new String("APPLET"), Tag.APPLET); + tags.put(new String("AREA"), Tag.AREA); + tags.put(new String("B"), Tag.B); + tags.put(new String("BASE"), Tag.BASE); + tags.put(new String("BASEFONT"), Tag.BASEFONT); + tags.put(new String("BIG"), Tag.BIG); + tags.put(new String("BLOCKQUOTE"), Tag.BLOCKQUOTE); + tags.put(new String("BODY"), Tag.BODY); + tags.put(new String("BR"), Tag.BR); + tags.put(new String("CAPTION"), Tag.CAPTION); + tags.put(new String("CENTER"), Tag.CENTER); + tags.put(new String("CITE"), Tag.CITE); + tags.put(new String("CODE"), Tag.CODE); + tags.put(new String("DD"), Tag.DD); + tags.put(new String("DFN"), Tag.DFN); + tags.put(new String("DIR"), Tag.DIR); + tags.put(new String("DIV"), Tag.DIV); + tags.put(new String("DL"), Tag.DL); + tags.put(new String("!DOCTYPE"), Type.DOCTYPE); + tags.put(new String("DT"), Tag.DT); + tags.put(new String("EM"), Tag.EM); + tags.put(new String("FONT"), Tag.FONT); + tags.put(new String("FORM"), Tag.FORM); + tags.put(new String("FRAME"), Tag.FRAME); + tags.put(new String("FRAMESET"), Tag.FRAMESET); + tags.put(new String("H1"), Tag.H1); + tags.put(new String("H2"), Tag.H2); + tags.put(new String("H3"), Tag.H3); + tags.put(new String("H4"), Tag.H4); + tags.put(new String("H5"), Tag.H5); + tags.put(new String("H6"), Tag.H6); + tags.put(new String("HEAD"), Tag.HEAD); + tags.put(new String("HTML"), Tag.HTML); + tags.put(new String("HR"), Tag.HR); + tags.put(new String("I"), Tag.I); + tags.put(new String("IMG"), Tag.IMG); + tags.put(new String("INPUT"), Tag.INPUT); + tags.put(new String("ISINDEX"), Tag.ISINDEX); + tags.put(new String("KBD"), Tag.KBD); + tags.put(new String("LI"), Tag.LI); + tags.put(new String("LABEL"), Type.LABEL); + tags.put(new String("LINK"), Tag.LINK); + tags.put(new String("MAP"), Tag.MAP); + tags.put(new String("MENU"), Tag.MENU); + tags.put(new String("META"), Tag.META); + tags.put(new String("NOFRAMES"), Tag.NOFRAMES); + tags.put(new String("OBJECT"), Tag.OBJECT); + tags.put(new String("OL"), Tag.OL); + tags.put(new String("OPTION"), Tag.OPTION); + tags.put(new String("P"), Tag.P); + tags.put(new String("PARAM"), Tag.PARAM); + tags.put(new String("PRE"), Tag.PRE); + tags.put(new String("S"), Tag.S); + tags.put(new String("SAMP"), Tag.SAMP); + tags.put(new String("SCRIPT"), Tag.SCRIPT); + tags.put(new String("SELECT"), Tag.SELECT); + tags.put(new String("SMALL"), Tag.SMALL); + tags.put(new String("STRONG"), Tag.STRONG); + tags.put(new String("STYLE"), Tag.STYLE); + tags.put(new String("SUB"), Tag.SUB); + tags.put(new String("SUP"), Tag.SUP); + tags.put(new String("TABLE"), Tag.TABLE); + tags.put(new String("TD"), Tag.TD); + tags.put(new String("TEXTAREA"), Tag.TEXTAREA); + tags.put(new String("TH"), Tag.TH); + tags.put(new String("THEAD"), Type.THEAD); + tags.put(new String("TITLE"), Tag.TITLE); + tags.put(new String("TR"), Tag.TR); + tags.put(new String("TT"), Tag.TT); + tags.put(new String("U"), Tag.U); + tags.put(new String("UL"), Tag.UL); + tags.put(new String("VAR"), Tag.VAR); + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/KeywordParser.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/KeywordParser.java new file mode 100644 index 000000000..719d96ba0 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/KeywordParser.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.core.internal; + +import java.io.IOException; +import java.io.Reader; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; + +import javax.security.auth.login.LoginException; + +import org.eclipse.mylar.bugzilla.core.internal.HtmlStreamTokenizer.Token; + + +/** + * Parses Bugzilla keywords page to determine keywords valid in this installation + * + * @author Shawn Minto + */ +public class KeywordParser +{ + /** Tokenizer used on the stream */ + private static HtmlStreamTokenizer tokenizer; + + /** + * Constructor. + * + * @param in + * The input stream for the keywords page. + */ + public KeywordParser(Reader in) { + tokenizer = new HtmlStreamTokenizer(in, null); + } + + /** + * Parse the keyword page for the valid products that a bug can be logged for + * + * @return A list of the keywordds that we can enter bugs for + * @throws IOException + * @throws ParseException + */ + public List<String> getKeywords() throws IOException, ParseException, LoginException { + ArrayList<String> keywords = new ArrayList<String>(); + + boolean isTitle = false; + boolean possibleBadLogin = false; + String title = ""; + + for (Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) { + + // make sure that bugzilla doesn't want us to login + if(token.getType() == Token.TAG && ((HtmlTag)(token.getValue())).getTagType() == HtmlTag.Type.TITLE && !((HtmlTag)(token.getValue())).isEndTag()) + { + isTitle = true; + continue; + } + + if(isTitle) + { + // get all of the data from inside of the title tag + if(token.getType() != Token.TAG) + { + title += ((StringBuffer)token.getValue()).toString().toLowerCase() + " "; + continue; + } + else if(token.getType() == Token.TAG && ((HtmlTag)token.getValue()).getTagType() == HtmlTag.Type.TITLE && ((HtmlTag)token.getValue()).isEndTag()) + { + // check if we may have a problem with login by looking at the title of the page + if((title.indexOf("login") != -1 || (title.indexOf("invalid") != -1 && title.indexOf("password") != -1) || title.indexOf("check e-mail") != -1 || title.indexOf("error") != -1)) + possibleBadLogin = true; + isTitle = false; + title = ""; + } + continue; + } + + if (token.getType() == Token.TAG ) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.TR && !tag.isEndTag()) + { + token = tokenizer.nextToken(); + if(token.getType() != Token.EOF && token.getType() == Token.TAG) + { + tag = (HtmlTag)token.getValue(); + if(tag.getTagType() != HtmlTag.Type.TH) + continue; + else + { + if(tag.getAttribute("align") == null || !"left".equalsIgnoreCase(tag.getAttribute("align"))) + parseKeywords(keywords); + + } + } + continue; + } + } + } + + // if we don't have any keywords and suspect that there was a login problem, assume we had a login problem + if(keywords == null && possibleBadLogin) + throw new LoginException("Bugzilla login information incorrect"); + return keywords; + } + + /** + * Parse the keywords that we can enter bugs for + * @param keywords The list of keywords to add this new product to + * @return + */ + private void parseKeywords(List<String> keywords) throws IOException, ParseException { + StringBuffer sb = new StringBuffer(); + + for (Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) + { + if(token.getType() == Token.TAG) + { + HtmlTag tag = (HtmlTag)token.getValue(); + if(tag.getTagType() == HtmlTag.Type.TH && (tag.isEndTag() || !"left".equalsIgnoreCase(tag.getAttribute("align")))) + break; + } + else if(token.getType() == Token.TEXT) + sb.append(token.toString()); + } + + String prod = HtmlStreamTokenizer.unescape(sb).toString(); + if(prod.endsWith(":")) + prod = prod.substring(0, prod.length() - 1); + keywords.add(prod); + + for (Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) + { + if(token.getType() == Token.TAG) + { + HtmlTag tag = (HtmlTag)token.getValue(); + if(tag.getTagType() == HtmlTag.Type.TR && tag.isEndTag()) + break; + + } + } + } + +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/NewBugParser.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/NewBugParser.java new file mode 100644 index 000000000..cc7327e90 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/NewBugParser.java @@ -0,0 +1,409 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.core.internal; + +import java.io.IOException; +import java.io.Reader; +import java.text.ParseException; + +import javax.security.auth.login.LoginException; + +import org.eclipse.mylar.bugzilla.core.Attribute; +import org.eclipse.mylar.bugzilla.core.internal.HtmlStreamTokenizer.Token; +import org.eclipse.mylar.bugzilla.ui.wizard.NewBugModel; + + +/** + * @author Shawn Minto + * + * This class parses the valid attribute values for a new bug + */ +public class NewBugParser { + /** Tokenizer used on the stream */ + private HtmlStreamTokenizer tokenizer; + + /** Flag for whether we need to try to get the product or not */ + private static boolean getProd = false; + + public NewBugParser(Reader in) { + tokenizer = new HtmlStreamTokenizer(in, null); + } + + /** + * Parse the new bugs valid attributes + * @param nbm A reference to a NewBugModel where all of the information is stored + * @throws IOException + * @throws ParseException + * @throws LoginException + */ + public void parseBugAttributes(NewBugModel nbm, boolean retrieveProducts) throws IOException, ParseException, LoginException + { + nbm.attributes.clear(); // clear any attriubtes in bug model from a previous product + + NewBugParser.getProd = retrieveProducts; + + // create a new bug report and set the parser state to the start state + ParserState state = ParserState.START; + String attribute = null; + + boolean isTitle = false; + boolean possibleBadLogin = false; + boolean isErrorState = false; + String title = ""; + // Default error message + String errorMsg = "Bugzilla could not get the needed bug attribute since your login name or password is incorrect. Please check your settings in the bugzilla preferences."; + + for (Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) { + // make sure that bugzilla doesn't want us to login + if(token.getType() == Token.TAG && ((HtmlTag)(token.getValue())).getTagType() == HtmlTag.Type.TITLE && !((HtmlTag)(token.getValue())).isEndTag()) { + isTitle = true; + continue; + } + + if(isTitle) { + // get all of the data in the title tag to compare with + if(token.getType() != Token.TAG) { + title += ((StringBuffer)token.getValue()).toString().toLowerCase() + " "; + continue; + } + else if(token.getType() == Token.TAG && ((HtmlTag)token.getValue()).getTagType() == HtmlTag.Type.TITLE && ((HtmlTag)token.getValue()).isEndTag()) { + // check if the title looks like we may have a problem with login + if(title.indexOf("login") != -1) { + possibleBadLogin = true; // generic / default msg passed to constructor re: bad login + } + if((title.indexOf("invalid") != -1 && title.indexOf("password") != -1) || title.indexOf("check e-mail") != -1 || title.indexOf("error") != -1) { + possibleBadLogin = true; + isErrorState = true; // set flag so appropriate msg is provide for the exception + errorMsg = ""; // error message will be parsed from error page + } + + isTitle = false; + title = ""; + } + continue; + } + + + // we have found the start of an attribute name + if ((state == ParserState.ATT_NAME || state == ParserState.START) && token.getType() == Token.TAG ) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.TD && "right".equalsIgnoreCase(tag.getAttribute("align"))) { + // parse the attribute's name + attribute = parseAttributeName(); + if(attribute == null) continue; + state = ParserState.ATT_VALUE; + continue; + } + + if (tag.getTagType() == HtmlTag.Type.TD && "#ff0000".equalsIgnoreCase(tag.getAttribute("bgcolor"))) { + state = ParserState.ERROR; + continue; + } + } + + // we have found the start of attribute values + if (state == ParserState.ATT_VALUE && token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.TD) { + // parse the attribute values + parseAttributeValue(nbm, attribute); + + state = ParserState.ATT_NAME; + attribute = null; + continue; + } + } + // page being parsed contains an Error message + // parse error message so it can be given to the constructor of the exception + // so an appropriate error message is displayed + if(state == ParserState.ERROR && isErrorState){ + // tag should be text token, not a tag + // get the error message + if(token.getType() == Token.TEXT) { + // get string value of next token to add to error messgage + // unescape the string so any escape sequences parsed appear unescaped in the details pane + errorMsg += HtmlStreamTokenizer.unescape( ((StringBuffer)token.getValue()).toString() ) + " "; + } + // expect </font> tag to indicate end of error end msg + // set next state to continue parsing remainder of page + else if(token.getType() == Token.TAG && ((HtmlTag)(token.getValue())).getTagType() == HtmlTag.Type.FONT && ((HtmlTag)(token.getValue())).isEndTag()) { + state = ParserState.ATT_NAME; + } + continue; + } + + if((state == ParserState.ATT_NAME || state == ParserState.START) && token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag)token.getValue(); + if(tag.getTagType() == HtmlTag.Type.INPUT && tag.getAttribute("type") != null &&"hidden".equalsIgnoreCase(tag.getAttribute("type").trim())) { + Attribute a = new Attribute(tag.getAttribute("name")); + a.setParameterName(tag.getAttribute("name")); + a.setValue(tag.getAttribute("value")); + a.setHidden(true); + nbm.attributes.put(a.getName(), a); + continue; + } + } + } + + // if we have no attributes and we suspect a bad login, we assume that the login info was bad + if(possibleBadLogin && (nbm.getAttributes() == null || nbm.getAttributes().size() == 0)) { + throw new LoginException(errorMsg); + } + } + + /** + * Parse the case where we have found an attribute name + * @param tokenizer The tokenizer to use to find the name + * @return The name of the attribute + * @throws IOException + * @throws ParseException + */ + private String parseAttributeName() + throws IOException, ParseException { + StringBuffer sb = new StringBuffer(); + + parseTableCell(sb); + HtmlStreamTokenizer.unescape(sb); + // remove the colon if there is one + if(sb.length() == 0) + return null; + if (sb.charAt(sb.length() - 1) == ':') { + sb.deleteCharAt(sb.length() - 1); + } + return sb.toString(); + } + + /** + * Reads text into a StringBuffer until it encounters a close table cell tag (</TD>) or start of another cell. + * The text is appended to the existing value of the buffer. <b>NOTE:</b> Does not handle nested cells! + * @param tokenizer + * @param sb + * @throws IOException + * @throws ParseException + */ + private void parseTableCell(StringBuffer sb) + throws IOException, ParseException { + boolean noWhitespace = false; + for (HtmlStreamTokenizer.Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) { + if (token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.TD) { + if (!tag.isEndTag()) { + tokenizer.pushback(token); + } + break; + } + noWhitespace = token.getWhitespace().length() == 0; + } + else if (token.getType() == Token.TEXT) { + // if there was no whitespace between the tag and the + // preceding text, don't insert whitespace before this text + // unless it is there in the source + if (!noWhitespace && token.getWhitespace().length() > 0 && sb.length() > 0) { + sb.append(' '); + } + sb.append((StringBuffer)token.getValue()); + } + } + } + + /** + * Parse the case where we have found attribute values + * @param nbm The NewBugModel that is to contain information about a new bug + * @param attributeName The name of the attribute that we are parsing + * @param tokenizer The tokenizer to use for parsing + * @throws IOException + * @throws ParseException + */ + private void parseAttributeValue( + NewBugModel nbm, + String attributeName) + throws IOException, ParseException { + + HtmlStreamTokenizer.Token token = tokenizer.nextToken(); + if (token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.SELECT && !tag.isEndTag()) { + String parameterName = tag.getAttribute("name"); + parseSelect(nbm, attributeName, parameterName); + } + else if (tag.getTagType() == HtmlTag.Type.INPUT && !tag.isEndTag()) { + parseInput(nbm, attributeName, tag); + } + else if (!tag.isEndTag()) { + parseAttributeValueCell(nbm, attributeName); + } + } + else { + StringBuffer sb = new StringBuffer(); + if (token.getType() == Token.TEXT) { + sb.append((StringBuffer) token.getValue()); + parseAttributeValueCell(nbm, attributeName, sb); + } + } + } + + /** + * Parse the case where the attribute value is just text in a table cell + * @param attributeName The name of the attribute we are parsing + * @param tokenizer The tokenizer to use for parsing + * @throws IOException + * @throws ParseException + */ + private void parseAttributeValueCell(NewBugModel nbm, String attributeName) + throws IOException, ParseException { + StringBuffer sb = new StringBuffer(); + + parseAttributeValueCell(nbm, attributeName, sb); + } + + private void parseAttributeValueCell( + NewBugModel nbm, + String attributeName, + StringBuffer sb) + throws IOException, ParseException { + + parseTableCell(sb); + HtmlStreamTokenizer.unescape(sb); + + // if we need the product we will get it + if(getProd && attributeName.equalsIgnoreCase("product")) { + nbm.setProduct(sb.toString()); + } + } + + /** + * Parse the case where the attribute value is an input + * @param nbm The new bug model to add information that we get to + * @param attributeName The name of the attribute that we are parsing + * @param tag The HTML tag that we are currently on + * @throws IOException + */ + private static void parseInput( + NewBugModel nbm, + String attributeName, + HtmlTag tag) + throws IOException { + + Attribute a = new Attribute(attributeName); + a.setParameterName(tag.getAttribute("name")); + String value = tag.getAttribute("value"); + if (value == null) value = ""; + + // if we found the summary, add it to the bug report + if (attributeName.equalsIgnoreCase("summary")) { + nbm.setSummary(value); + } + else if (attributeName.equalsIgnoreCase("Attachments")) { + // do nothing - not a problem after 2.14 + } + else if (attributeName.equalsIgnoreCase("add cc")) { + // do nothing + } + else if (attributeName.toLowerCase().startsWith("cc")) { + // do nothing cc's are options not inputs + } + else { + // otherwise just add the attribute + a.setValue(value); + nbm.attributes.put(attributeName, a); + } + } + + /** + * Parse the case where the attribute value is an option + * @param nbm The NewBugModel that we are storing information in + * @param attributeName The name of the attribute that we are parsing + * @param parameterName The SELECT tag's name + * @param tokenizer The tokenizer that we are using for parsing + * @throws IOException + * @throws ParseException + */ + private void parseSelect( + NewBugModel nbm, + String attributeName, + String parameterName) + throws IOException, ParseException { + + boolean first = false; + Attribute a = new Attribute(attributeName); + a.setParameterName(parameterName); + + HtmlStreamTokenizer.Token token = tokenizer.nextToken(); + while ( token.getType() != Token.EOF) { + if (token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.SELECT && tag.isEndTag()) break; + if (tag.getTagType() == HtmlTag.Type.OPTION && !tag.isEndTag()) { + String optionName = tag.getAttribute("value"); + boolean selected = tag.hasAttribute("selected"); + StringBuffer optionText = new StringBuffer(); + for (token = tokenizer.nextToken(); token.getType() == Token.TEXT; token = tokenizer.nextToken()) { + if (optionText.length() > 0) { + optionText.append(' '); + } + optionText.append((StringBuffer) token.getValue()); + } + a.addOptionValue(optionText.toString(), optionName); + + if (selected || first) { + a.setValue(optionText.toString()); + first = false; + } + } + else { + token = tokenizer.nextToken(); + } + } + else { + token = tokenizer.nextToken(); + } + } + + if(!(nbm.attributes).containsKey(attributeName)) { + (nbm.attributes).put(attributeName, a); + } + } + + /** + * Enum class for describing current state of Bugzilla report parser. + */ + private static class ParserState + { + /** An instance of the start state */ + protected static final ParserState START = new ParserState("start"); + + /** An instance of the state when the parser found an attribute name */ + protected static final ParserState ATT_NAME = new ParserState("att_name"); + + /** An instance of the state when the parser found an attribute value */ + protected static final ParserState ATT_VALUE = new ParserState("att_value"); + /** An instance of the state when an error page is found */ + protected static final ParserState ERROR = new ParserState("error"); + /** State's human-readable name */ + private String name; + + /** + * Constructor + * @param description - The states human readable name + */ + private ParserState(String description) + { + this.name = description; + } + + @Override + public String toString() + { + return name; + } + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/ProductConfiguration.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/ProductConfiguration.java new file mode 100644 index 000000000..3e6542f69 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/ProductConfiguration.java @@ -0,0 +1,177 @@ +/******************************************************************************* + * Copyright (c) 2004 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.core.internal; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Class describing the configuration of products and components for a given + * Bugzilla installation. + */ +public class ProductConfiguration implements Serializable { + + /** Automatically generated serialVersionUID */ + private static final long serialVersionUID = 3257004354337519410L; + + private Map<String, ProductEntry> products = new HashMap<String, ProductEntry>(); + + public ProductConfiguration() { + super(); + } + + /** + * Adds a product to the configuration. + */ + public void addProduct(String name) { + if (!products.containsKey(name)) { + ProductEntry product = new ProductEntry(name); + products.put(name, product); + } + } + + /** + * Returns an array of names of current products. + */ + public String[] getProducts() { + return products.keySet().toArray(new String[0]); + } + + /** + * Returns an array of names of component that exist for a given product + * or <code>null</code> if the product does not exist. + */ + public String[] getComponents(String product) { + ProductEntry entry = products.get(product); + if (entry != null) { + return entry.getComponents(); + } + else return null; + } + + /** + * Returns an array of names of versions that exist for a given product + * or <code>null</code> if the product does not exist. + */ + public String[] getVersions(String product) { + ProductEntry entry = products.get(product); + if (entry != null) { + return entry.getVersions(); + } + else return null; + } + + /** + * Returns an array of names of valid severity values. + */ + public String[] getSeverities() { + return new String[] {"blocker", "critical", "major", "normal", "minor", "trivial", "enhancement"}; + } + + /** + * Returns an array of names of valid OS values. + */ + public String[] getOSs() { + return new String[] {"All", "Windows XP", "Linux", "other"}; + } + + /** + * Returns an array of names of valid platform values. + */ + public String[] getPlatforms() { + return new String[] {"All", "Macintosh", "PC"}; + } + + /** + * Returns an array of names of valid platform values. + */ + public String[] getPriorities() { + return new String[] {"P1", "P2", "P3", "P4", "P5"}; + } + + /** + * Adds a component to the given product. + */ + public void addComponent(String product, String component) { + ProductEntry entry = products.get(product); + if (entry == null) { + entry = new ProductEntry(product); + products.put(product, entry); + } + entry.addComponent(component); + } + + /** + * Adds a list of components to the given product. + */ + public void addComponents(String product, String[] components) { + ProductEntry entry = products.get(product); + if (entry == null) { + entry = new ProductEntry(product); + products.put(product, entry); + } + for (int i = 0; i < components.length; i++) { + String component = components[i]; + entry.addComponent(component); + } + } + + /** + * Adds a list of components to the given product. + */ + public void addVersions(String product, String[] versions) { + ProductEntry entry = products.get(product); + if (entry == null) { + entry = new ProductEntry(product); + products.put(product, entry); + } + for (int i = 0; i < versions.length; i++) { + String version = versions[i]; + entry.addVersion(version); + } + } + + /** + * Container for product information: name, components. + */ + private static class ProductEntry implements Serializable { + + /** Automatically generated serialVersionUID */ + private static final long serialVersionUID = 3977018465733391668L; + + String productName; + List<String> components = new ArrayList<String>(); + List<String> versions = new ArrayList<String>(); + + ProductEntry(String name) { + this.productName = name; + } + String[] getComponents() { + return components.toArray(new String[0]); + } + void addComponent(String componentName) { + if (!components.contains(componentName)) { + components.add(componentName); + } + } + String[] getVersions() { + return versions.toArray(new String[0]); + } + void addVersion(String name) { + if (!versions.contains(name)) { + versions.add(name); + } + } + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/ProductConfigurationFactory.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/ProductConfigurationFactory.java new file mode 100644 index 000000000..b7736df57 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/ProductConfigurationFactory.java @@ -0,0 +1,194 @@ +/******************************************************************************* + * Copyright (c) 2004 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.core.internal; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; + +import org.eclipse.mylar.bugzilla.BugzillaPlugin; + + +/** + * A factory for creating ProductConfiguration objects that encapsulate valid + * combinations of products, components, and versions. + */ +public class ProductConfigurationFactory { + /** Singleton factory instance */ + private static ProductConfigurationFactory instance; + + /** + * Private constructor to ensure singleton instances. + */ + private ProductConfigurationFactory() { + // no initial setup needed + } + + /** + * Returns the factory singletoninstance. + */ + public static synchronized ProductConfigurationFactory getInstance() { + if (instance == null) { + instance = new ProductConfigurationFactory(); + } + return instance; + } + + /** + * Builds a ProductConfiguration object by parsing the source of the + * Bugzilla query page. + */ + public ProductConfiguration getConfiguration(String server) throws IOException { + URL serverURL = new URL(server + "/query.cgi"); + ProductConfiguration configuration = new ProductConfiguration(); + ArrayList<String []> componentsMatrix = new ArrayList<String []>(); + ArrayList<String []> versionsMatrix = new ArrayList<String []>(); + URLConnection c = serverURL.openConnection(); + BufferedReader in = new BufferedReader(new InputStreamReader(c.getInputStream())); + String line; + while ((line = in.readLine()) != null) { + if (line.startsWith(" cpts[")) { + String [] components = parseComponents(line); + if (components.length > 0) componentsMatrix.add(components); + } + else if (line.startsWith(" vers[")) { + String [] versions = parseComponents(line); + if (versions.length > 0) versionsMatrix.add(versions); + } + else if (line.indexOf("<select name=\"product\"") != -1) { + String [] products = parseProducts(in); + for (int i = 0; i < products.length; i++) { + String product = products[i]; + configuration.addProduct(product); + // If components don't jibe with the products, just don't make them available. + if (products.length == componentsMatrix.size()) { + configuration.addComponents(product, componentsMatrix.get(i)); + } + + // If versions don't jibe with the products, just don't make them available + if (products.length == versionsMatrix.size()) { + configuration.addVersions(product, versionsMatrix.get(i)); + } + } + } + } + return configuration; + } + + /** + * Returns an array of valid components or versions by parsing the JavaScript + * array in the Bugzilla query page. + */ + protected String [] parseComponents(String line) { + ArrayList<String> components = new ArrayList<String>(); + int start = line.indexOf('\''); + if (start >= 0) { + boolean inName = true; + StringBuffer name = new StringBuffer(); + for (int i = start+1; i < line.length(); i++){ + char ch = line.charAt(i); + if (inName) { + if (ch == '\'') { + components.add(name.toString()); + name.setLength(0); + inName = false; + } + else name.append(ch); + } + else { + if (ch == '\'') { + inName = true; + } + } + } + } + return components.toArray(new String[0]); + } + + /** + * Returns an array of valid product names by parsing the product selection list + * in the Bugzilla query page. + */ + protected String[] parseProducts(BufferedReader in) throws IOException { + ArrayList<String> products = new ArrayList<String>(); + String line; + while ((line = in.readLine()) != null) { + if (line.indexOf("</select>") != -1) break; + int optionIndex = line.indexOf("<option value=\""); + if (optionIndex != -1) { + boolean inName = false; + StringBuffer name = new StringBuffer(); + for (int i = optionIndex; i< line.length(); i++) { + char ch = line.charAt(i); + if (inName) { + if (ch == '<') { + products.add(name.toString()); + break; + } + else name.append(ch); + } + else { + if (ch == '>') { + inName = true; + } + } + } + } + } + return products.toArray(new String[0]); + } + + /** + * Restores a ProductConfiguration from a file. + */ + public ProductConfiguration readConfiguration(File file) throws IOException { + if (!file.exists()) return null; + FileInputStream fin = null; + ProductConfiguration configuration = null; + try { + fin = new FileInputStream(file); + ObjectInputStream in = new ObjectInputStream(fin); + configuration = (ProductConfiguration) in.readObject(); + } catch (ClassNotFoundException e) { + BugzillaPlugin.log(e); + IOException ex = new IOException(); + ex.initCause(e); + throw ex; + } finally { + if (fin != null) + fin.close(); + } + return configuration; + } + + /** + * Saves a ProductConfiguration to a file. + */ + public void writeConfiguration(ProductConfiguration configuration, File file) throws IOException { + FileOutputStream fout = null; + try { + fout = new FileOutputStream(file); + ObjectOutputStream out = new ObjectOutputStream(fout); + out.writeObject(configuration); + } finally { + if (fout != null) + fout.close(); + } + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/ProductParser.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/ProductParser.java new file mode 100644 index 000000000..b33b7e706 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/ProductParser.java @@ -0,0 +1,148 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.core.internal; + +import java.io.IOException; +import java.io.Reader; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; + +import javax.security.auth.login.LoginException; + +import org.eclipse.mylar.bugzilla.core.internal.HtmlStreamTokenizer.Token; + + + +/** + * @author Shawn Minto + * + * This class is used to parse the available products to log a bug for + */ +public class ProductParser +{ + /** Tokenizer used on the stream */ + private HtmlStreamTokenizer tokenizer; + + public ProductParser(Reader in) { + tokenizer = new HtmlStreamTokenizer(in, null); + } + + /** + * Parse the product page for the valid products that a bug can be logged for + * @param in The input stream for the products page + * @return A list of the products that we can enter bugs for + * @throws IOException + * @throws ParseException + */ + public List<String> getProducts() throws IOException, ParseException, LoginException + { + ArrayList<String> products = null; + + boolean isTitle = false; + boolean possibleBadLogin = false; + String title = ""; + + for (HtmlStreamTokenizer.Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) { + + // make sure that bugzilla doesn't want us to login + if(token.getType() == Token.TAG && ((HtmlTag)(token.getValue())).getTagType() == HtmlTag.Type.TITLE && !((HtmlTag)(token.getValue())).isEndTag()) + { + isTitle = true; + continue; + } + + if(isTitle) + { + // get all of the data in the title tag + if(token.getType() != Token.TAG) + { + title += ((StringBuffer)token.getValue()).toString().toLowerCase() + " "; + continue; + } + else if(token.getType() == Token.TAG && ((HtmlTag)token.getValue()).getTagType() == HtmlTag.Type.TITLE && ((HtmlTag)token.getValue()).isEndTag()) + { + // compare the title to see if we think that there is a problem with login + if((title.indexOf("login") != -1 || (title.indexOf("invalid") != -1 && title.indexOf("password") != -1) || title.indexOf("check e-mail") != -1 || title.indexOf("error") != -1)) + possibleBadLogin = true; + isTitle = false; + title = ""; + } + continue; + } + + if (token.getType() == Token.TAG ) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.TR) + { + token = tokenizer.nextToken(); + if(token.getType() != Token.EOF && token.getType() == Token.TAG) + { + tag = (HtmlTag)token.getValue(); + if(tag.getTagType() != HtmlTag.Type.TH) + continue; + else + { + if(products == null) + products = new ArrayList<String>(); + parseProducts(products); + + } + } + continue; + } + } + } + + // if we have no products and we suspect a login error, assume that it was a login error + if(products == null && possibleBadLogin) + throw new LoginException("Bugzilla login information incorrect"); + return products; + } + + /** + * Parse the products that we can enter bugs for + * @param products The list of products to add this new product to + * @return + */ + private void parseProducts(List<String> products) throws IOException, ParseException + { + StringBuffer sb = new StringBuffer(); + + for (HtmlStreamTokenizer.Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) + { + if(token.getType() == Token.TAG) + { + HtmlTag tag = (HtmlTag)token.getValue(); + if(tag.getTagType() == HtmlTag.Type.TH && tag.isEndTag()) + break; + } + else if(token.getType() == Token.TEXT) + sb.append(token.toString()); + } + + String prod = HtmlStreamTokenizer.unescape(sb).toString(); + if(prod.endsWith(":")) + prod = prod.substring(0, prod.length() - 1); + products.add(prod); + + for (HtmlStreamTokenizer.Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) + { + if(token.getType() == Token.TAG) + { + HtmlTag tag = (HtmlTag)token.getValue(); + if(tag.getTagType() == HtmlTag.Type.TR && tag.isEndTag()) + break; + + } + } + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/Favorite.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/Favorite.java new file mode 100644 index 000000000..dfed35c45 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/Favorite.java @@ -0,0 +1,110 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.favorites; + +import java.io.Serializable; +import java.util.Date; +import java.util.Map; + +import org.eclipse.mylar.bugzilla.core.BugReport; +import org.eclipse.mylar.bugzilla.search.BugzillaSearchResultCollector; + + +/** + * Class representing an item in the favorites view + */ +public class Favorite implements Serializable { + + /** Automatically generated serialVersionUID */ + private static final long serialVersionUID = 3258129158977632310L; + + /** Bug id */ + private int id; + /** Bug description */ + private String description; + /** Query that created the match */ + private String query; + /** Bug's attributes (severity, priority, etc.) */ + private Map<String, Object> attributes; + /** Date when the favorite was recommended. */ + private Date date; + + private String server; + + /** + * Constructor. + * + * @param bug + * The bug this favorite represents. + */ + public Favorite(BugReport bug) { + this(bug.getServer(), bug.getId(), bug.getSummary(), "", BugzillaSearchResultCollector.getAttributeMap(bug)); + } + + /** + * Constructor. + */ + public Favorite(String server, int id, String description, String query, Map<String, Object> attributes) { + this.server = server; + this.id = id; + this.description = description; + this.query = query; + this.attributes = attributes; + date = new Date(); + } + + /** + * returns the server for the bug + */ + public String getServer(){ + return server; + } + + /** + * Returns bug's id. + */ + public int getId() { + return id; + } + + /** + * Returns bug's description. + */ + public String getDescription() { + return description; + } + + /** + * Returns bug attributes. + */ + public Map<String, Object> getAttributes() { + return attributes; + } + + /** + * Returns the query that created the match. + */ + public String getQuery() { + return query; + } + + /** + * Returns date when the favorite was added to the view. + */ + public Date getDate() { + return date; + } + + @Override + public String toString() { + return id + " - " + description; + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/FavoritesFile.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/FavoritesFile.java new file mode 100644 index 000000000..4742eee3c --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/FavoritesFile.java @@ -0,0 +1,318 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.favorites; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.mylar.bugzilla.BugzillaPlugin; +import org.eclipse.mylar.bugzilla.IBugzillaConstants; + + +/** + * Class to persist the data for the favorites list + */ +public class FavoritesFile +{ + /** The file that the favorites are written to */ + private File file; + + /** The directory to where the file is located */ + /** A list of favorites */ + private ArrayList<Favorite> list = new ArrayList<Favorite>(); + + + /** Sort by bug ID */ + public static final int ID_SORT = 0; + + /** Sort by bug priority */ + public static final int PRIORITY_SORT = 1; + + /** Sort by bug priority */ + public static final int SEVERITY_SORT = 2; + + /** Sort by bug state */ + public static final int STATE_SORT = 3; + + /** Default sort by bug ID */ + public static int lastSel = 0; + + /** + * Constructor that reads the favorites data persisted in the plugin's state + * directory, if it exists. + * + * @param file + * The file where the favorites are persisted + * @throws IOException + * Error opening or closing the favorites file + * @throws ClassNotFoundException + * Error deserializing objects from the favorites file + */ + public FavoritesFile(File file) throws ClassNotFoundException, IOException { + this.file = file; + if (file.exists()) { + readFile(); + } + } + + /** + * Add a favorite to the favorites list + * @param entry The bug to add + */ + public void add(Favorite entry) { + // add the entry to the list and write the file to disk + list.add(entry); + writeFile(); + } + + /** + * Find a bug in the favorites list + * @param id The bug id that we are looking for + * @return The index of the bug in the array if it exists, else 0 + */ + public int find(int id) { + for (int i = 0; i < list.size(); i++) { + Favorite currFav = list.get(i); + if (currFav.getId() == id) + return i; + } + return 0; + } + + /** + * Get the list of favorites + * @return The list of favorites + */ + public ArrayList<Favorite> elements() { + return list; + } + + /** + * Write the favorites to disk + */ + private void writeFile() { + try { + ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file)); + + // Write the size of the list so that we can read it back in easier + out.writeInt(list.size()); + + // write each element in the array list + for (int i = 0; i < list.size(); i++) { + Object item = list.get(i); + out.writeObject(item); + } + out.close(); + } + catch (IOException e) { + // put up a message and log the error if there is a problem writing to the file + MessageDialog.openError(null, + "I/O Error", + "Bugzilla could not write to favorites file."); + BugzillaPlugin.log(e); + } + } + + /** + * Read the favorites in from the file on disk + * + * @throws IOException + * Error opening or closing the favorites file + * @throws ClassNotFoundException + * Error deserializing objects from the favorites file + */ + private void readFile() throws ClassNotFoundException, IOException { + ObjectInputStream in = new ObjectInputStream(new FileInputStream(file)); + + // get the number of favorites in the file + int size = in.readInt(); + + // read in each of the favorites in the file + for (int nX = 0; nX < size; nX++) { + Favorite item = (Favorite) in.readObject(); + // add the favorite to the favorites list + list.add(item); + } + in.close(); + + sort(lastSel); + } + + /** + * Remove some bugs from the favorites list + * @param indicesToRemove An array of the indicies of the bugs to be removed + */ + public void remove(List<Favorite> sel) { + list.removeAll(sel); + + // rewrite the file so that the data is persistant + writeFile(); + } + + /** + * Remove all of the items in the favortes menu + */ + public void removeAll() { + list.clear(); + + // rewrite the file so that the data is persistant + writeFile(); + } + + /** + * Function to sort the favorites list + * @param sortOrder The way to sort the bugs in the favorites list + */ + public void sort(int sortOrder) { + Favorite[] a = list.toArray(new Favorite[list.size()]); + + // decide which sorting method to use and sort the favorites + switch(sortOrder) { + case ID_SORT: + Arrays.sort(a, new SortID()); + lastSel = ID_SORT; + break; + case PRIORITY_SORT: + Arrays.sort(a, new SortPriority()); + lastSel = PRIORITY_SORT; + break; + + case SEVERITY_SORT: + Arrays.sort(a, new SortSeverity()); + lastSel = SEVERITY_SORT; + break; + + case STATE_SORT: + Arrays.sort(a, new SortState()); + lastSel = STATE_SORT; + break; + } + + // remove all of the elements from the list so that we can re-add + // them in a sorted order + list.clear(); + + // add the sorted elements to the list and the table + for (int j = 0; j < a.length; j++) { + add(a[j]); + } + } + + /** + * Inner class to sort by bug id + */ + private class SortID implements Comparator<Favorite> { + /** + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + public int compare(Favorite f1, Favorite f2) { + Integer id1 = (Integer) f1.getAttributes().get(IBugzillaConstants.HIT_MARKER_ATTR_ID); + Integer id2 = (Integer) f2.getAttributes().get(IBugzillaConstants.HIT_MARKER_ATTR_ID); + + if(id1 != null && id2 != null) + return id1.compareTo(id2); + else if(id1 == null && id2 != null) + return -1; + else if(id1 != null && id2 == null) + return 1; + else + return 0; + } + } + + /** + * Inner class to sort by priority + */ + private class SortPriority implements Comparator<Favorite> + { + /* + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + public int compare(Favorite f1, Favorite f2) { + Integer pri1 = (Integer) f1.getAttributes().get(IBugzillaConstants.HIT_MARKER_ATTR_PRIORITY); + Integer pri2 = (Integer) f2.getAttributes().get(IBugzillaConstants.HIT_MARKER_ATTR_PRIORITY); + + if(pri1 != null && pri2 != null) + return pri1.compareTo(pri2); + else if(pri1 == null && pri2 != null) + return -1; + else if(pri1 != null && pri2 == null) + return 1; + else + return 0; + } + } + + /** + * Inner class to sort by severity + */ + private class SortSeverity implements Comparator<Favorite> { + /* + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + public int compare(Favorite f1, Favorite f2) { + Integer sev1 = (Integer) f1.getAttributes().get(IBugzillaConstants.HIT_MARKER_ATTR_SEVERITY); + Integer sev2 = (Integer) f2.getAttributes().get(IBugzillaConstants.HIT_MARKER_ATTR_SEVERITY); + + if(sev1 != null && sev2 != null) + return sev1.compareTo(sev2); + else if(sev1 == null && sev2 != null) + return -1; + else if(sev1 != null && sev2 == null) + return 1; + else + return 0; + } + } + + /** + * Inner class to sort by state + */ + private class SortState implements Comparator<Favorite> + { + /* + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + public int compare(Favorite f1, Favorite f2) { + Integer sta1 = (Integer) f1.getAttributes().get(IBugzillaConstants.HIT_MARKER_ATTR_STATE); + Integer sta2 = (Integer) f2.getAttributes().get(IBugzillaConstants.HIT_MARKER_ATTR_STATE); + + if(sta1 != null && sta2 != null) + { + int rc = sta1.compareTo(sta2); + if(rc == 0) + { + Integer res1 = (Integer) f1.getAttributes().get(IBugzillaConstants.HIT_MARKER_ATTR_RESULT); + Integer res2 = (Integer) f2.getAttributes().get(IBugzillaConstants.HIT_MARKER_ATTR_RESULT); + + return res1.compareTo(res2); + } + else + return rc; + } + else if(sta1 == null && sta2 != null) + return -1; + else if(sta1 != null && sta2 == null) + return 1; + else + return 0; + } + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/actions/AbstractFavoritesAction.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/actions/AbstractFavoritesAction.java new file mode 100644 index 000000000..f64e4a7b7 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/actions/AbstractFavoritesAction.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.favorites.actions; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.mylar.bugzilla.BugzillaPlugin; +import org.eclipse.swt.widgets.Shell; + + +/** + * Class that contains shared functions for the favorites actions + */ +public class AbstractFavoritesAction extends Action +{ + + /** + * Set the actions icon + * @param filename The icons file + */ + protected void setIcon(String filename) + { + URL url = null; + try + { + // try to change the default icon + url = new URL(BugzillaPlugin.getDefault().getBundle().getEntry("/"), filename); + setImageDescriptor(ImageDescriptor.createFromURL(url)); + } + catch (MalformedURLException e) + { + // Simply don't change the default icon + } + } + + /** + * Convienience method for getting the current shell + * @return The current shell + */ + protected Shell getShell() + { + return BugzillaPlugin.getDefault().getWorkbench().getActiveWorkbenchWindow().getShell(); + } + +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/actions/AddFavoriteAction.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/actions/AddFavoriteAction.java new file mode 100644 index 000000000..1a342d61e --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/actions/AddFavoriteAction.java @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.favorites.actions; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.mylar.bugzilla.BugzillaPlugin; +import org.eclipse.mylar.bugzilla.IBugzillaConstants; +import org.eclipse.mylar.bugzilla.favorites.Favorite; +import org.eclipse.mylar.bugzilla.search.BugzillaSearchResultView; +import org.eclipse.mylar.bugzilla.ui.FavoritesView; + + +/** + * Bookmark a returned Bugzilla Search result. + */ +public class AddFavoriteAction extends AbstractFavoritesAction { + + /** Selected objects */ + private List<Favorite> selected; + + /** The view where the Bugzilla search results are displayed */ + private BugzillaSearchResultView resultView; + + /** + * Constructor + * @param text The label for the context menu item + * @param resultView The view where the Bugzilla search results are displayed + */ + public AddFavoriteAction(String text, BugzillaSearchResultView resultView) { + setText(text); + setIcon("Icons/bugzilla-bookmark.gif"); + this.resultView = resultView; + selected = null; + } + + /** + * Bookmark the selected items + * @see org.eclipse.ui.actions.ActionDelegate#run(IAction) + */ + @Override + public void run() + { + FavoritesView.checkWindow(); + + selected = new ArrayList<Favorite>(); + buildSelection(); + + // go through each of the selected items and add its data to the favorites file + for (int i = 0; i < selected.size(); i++) + { + BugzillaPlugin.getDefault().getFavorites().add(selected.get(i)); + } + FavoritesView.add(); + FavoritesView.updateActionEnablement(); + } + + /** + * Gets the entire selection and puts each bug report into a list. Only the + * relevent parts of each bug report are put into the list. + */ + @SuppressWarnings("unchecked") + private void buildSelection() { + ISelection s = resultView.getViewer().getSelection(); + if (s instanceof IStructuredSelection) { + IStructuredSelection selection = (IStructuredSelection) s; + for (Iterator<IMarker> it = selection.iterator(); it.hasNext();) { + IMarker marker = it.next(); + + try + { + // try to get the attribute for the marker + Integer attributeId = (Integer) marker.getAttribute(IBugzillaConstants.HIT_MARKER_ATTR_ID); + // add the selected item to the list of items that are selected + String description = (String) marker.getAttribute(IBugzillaConstants.HIT_MARKER_ATTR_DESC); + String query = (String) marker.getAttribute(IBugzillaConstants.HIT_MARKER_ATTR_QUERY); + + // create a map to add attributes to so that the favorites file can sort + HashMap<String, Object> attributes = new HashMap<String, Object>(); + attributes.put(IBugzillaConstants.HIT_MARKER_ATTR_ID, attributeId); + attributes.put(IBugzillaConstants.HIT_MARKER_ATTR_PRIORITY, marker.getAttribute(IBugzillaConstants.HIT_MARKER_ATTR_PRIORITY)); + attributes.put(IBugzillaConstants.HIT_MARKER_ATTR_SEVERITY, marker.getAttribute(IBugzillaConstants.HIT_MARKER_ATTR_SEVERITY)); + attributes.put(IBugzillaConstants.HIT_MARKER_ATTR_STATE, marker.getAttribute(IBugzillaConstants.HIT_MARKER_ATTR_STATE)); + attributes.put(IBugzillaConstants.HIT_MARKER_ATTR_RESULT, marker.getAttribute(IBugzillaConstants.HIT_MARKER_ATTR_RESULT)); + + Favorite favorite = new Favorite(BugzillaPlugin.getDefault().getServerName(), attributeId.intValue(), description, query, attributes); + selected.add(favorite); + } + catch (CoreException ignored) + { + // do nothing + } + } + } + } + +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/actions/AddToFavoritesAction.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/actions/AddToFavoritesAction.java new file mode 100644 index 000000000..65761203c --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/actions/AddToFavoritesAction.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.favorites.actions; + +import org.eclipse.jface.action.Action; +import org.eclipse.mylar.bugzilla.BugzillaImages; +import org.eclipse.mylar.bugzilla.BugzillaPlugin; +import org.eclipse.mylar.bugzilla.favorites.Favorite; +import org.eclipse.mylar.bugzilla.ui.FavoritesView; +import org.eclipse.mylar.bugzilla.ui.editor.ExistingBugEditorInput; +import org.eclipse.ui.part.EditorPart; + + +/** + * Action used to add the supplied editor's bug to the favorites list. + */ +public class AddToFavoritesAction extends Action { + private EditorPart editorPart; + + /** + * Creates a new <code>AddToFavoritesAction</code>. + * + * @param editor + * The editor for the bug that is being added to the favorites + * list. + */ + public AddToFavoritesAction(EditorPart editor) { + editorPart = editor; + setText("&Add to favorites"); + setImageDescriptor(BugzillaImages.getImageDescriptor(BugzillaImages.IMG_TOOL_ADD_TO_FAVORITES)); + } + + @Override + public void run() { + ExistingBugEditorInput input = (ExistingBugEditorInput) editorPart.getEditorInput(); + Favorite entry = new Favorite(input.getBug()); + FavoritesView.checkWindow(); + BugzillaPlugin.getDefault().getFavorites().add(entry); + FavoritesView.add(); + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/actions/DeleteFavoriteAction.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/actions/DeleteFavoriteAction.java new file mode 100644 index 000000000..6a8bdab46 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/actions/DeleteFavoriteAction.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.favorites.actions; + +import org.eclipse.mylar.bugzilla.ui.FavoritesView; + +/** + * Action of removing a bookmark + */ +public class DeleteFavoriteAction extends AbstractFavoritesAction +{ + /** The instance of the favorites view */ + private FavoritesView view; + + /** True if all of the bookmarks are to be deleted */ + private boolean deleteAll; + + /** + * Constructor + * @param favoritesView The favorites view being used + * @param deleteAllFavorites <code>true</code> if all of the favorites should be deleted, else <code>false</code> + */ + public DeleteFavoriteAction(FavoritesView favoritesView, boolean deleteAllFavorites) + { + deleteAll = deleteAllFavorites; + + // set the appropriate icons and tool tips for the action depending + // on whether it will delete all items or not + if (deleteAll) + { + setToolTipText("Remove all favorites"); + setText("Remove all"); + setIcon("Icons/remove-all.gif"); + } + else + { + setToolTipText( "Remove selected favorites" ); + setText( "Remove" ); + setIcon( "Icons/remove.gif" ); + } + + view = favoritesView; + } + + /** + * Delete the appropriate favorites + * @see org.eclipse.jface.action.IAction#run() + */ + @Override + public void run() + { + FavoritesView.checkWindow(); + + // call the appropriate delete function + if (deleteAll) + view.deleteAllFavorites(); + else + view.deleteSelectedFavorites(); + FavoritesView.updateActionEnablement(); + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/actions/ViewFavoriteAction.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/actions/ViewFavoriteAction.java new file mode 100644 index 000000000..3ed3da1e5 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/actions/ViewFavoriteAction.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.favorites.actions; + +import java.util.List; + +import org.eclipse.mylar.bugzilla.ui.BugzillaOpenStructure; +import org.eclipse.mylar.bugzilla.ui.FavoritesView; +import org.eclipse.mylar.bugzilla.ui.ViewBugzillaAction; + + +/** + * View a bug from the favorites menu + */ +public class ViewFavoriteAction extends AbstractFavoritesAction +{ + + /** The view to get the result to launch a viewer on */ + private FavoritesView view; + + /** + * Constructor + * @param resultsView The view to launch a viewer on + */ + public ViewFavoriteAction( FavoritesView resultsView ) + { + setToolTipText( "View selected favorites" ); + setText( "View selected" ); + setIcon( "Icons/openresult.gif" ); + view = resultsView; + } + + /** + * View the selected bugs in the editor window + * @see org.eclipse.jface.action.IAction#run() + */ + @Override + public void run() + { + FavoritesView.checkWindow(); + List<BugzillaOpenStructure> selectedBugs = view.getBugIdsOfSelected(); + + // if there are some selected bugs view the bugs in the editor window + if (!selectedBugs.isEmpty()) + { + ViewBugzillaAction viewBugs = new ViewBugzillaAction("Display bugs in editor", selectedBugs); + viewBugs.schedule(); + } + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/offlineReports/AbstractOfflineReportsAction.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/offlineReports/AbstractOfflineReportsAction.java new file mode 100644 index 000000000..f50d0ece6 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/offlineReports/AbstractOfflineReportsAction.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.offlineReports; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.mylar.bugzilla.BugzillaPlugin; +import org.eclipse.swt.widgets.Shell; + + +/** + * Class that contains shared functions for the offline report actions + */ +public class AbstractOfflineReportsAction extends Action +{ + + /** + * Set the actions icon + * @param filename The icons file + */ + protected void setIcon(String filename) + { + URL url = null; + try + { + // try to change the default icon + url = new URL(BugzillaPlugin.getDefault().getBundle().getEntry("/"), filename); + setImageDescriptor(ImageDescriptor.createFromURL(url)); + } + catch (MalformedURLException e) + { + // Simply don't change the default icon + } + } + + /** + * Convienience method for getting the current shell + * @return The current shell + */ + protected Shell getShell() + { + return BugzillaPlugin.getDefault().getWorkbench().getActiveWorkbenchWindow().getShell(); + } + +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/offlineReports/DeleteOfflineReportAction.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/offlineReports/DeleteOfflineReportAction.java new file mode 100644 index 000000000..ac66960c5 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/offlineReports/DeleteOfflineReportAction.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.offlineReports; + +import org.eclipse.mylar.bugzilla.ui.OfflineView; + +/** + * Action of removing a bookmark + */ +public class DeleteOfflineReportAction extends AbstractOfflineReportsAction +{ + /** The instance of the offlineReports view */ + private OfflineView view; + + /** True if all of the bookmarks are to be deleted */ + private boolean deleteAll; + + /** + * Constructor + * @param offlineReportsView The offlineReports view being used + * @param deleteAllOfflineReports <code>true</code> if all of the offlineReports should be deleted, else <code>false</code> + */ + public DeleteOfflineReportAction(OfflineView offlineReportsView, boolean deleteAllOfflineReports) + { + deleteAll = deleteAllOfflineReports; + + // set the appropriate icons and tool tips for the action depending + // on whether it will delete all items or not + if (deleteAll) + { + setToolTipText("Remove all offline reports"); + setText("Remove all"); + setIcon("Icons/remove-all.gif"); + } + else + { + setToolTipText( "Remove selected offline reports" ); + setText( "Remove" ); + setIcon( "Icons/remove.gif" ); + } + + view = offlineReportsView; + } + + /** + * Delete the appropriate offline reports + * @see org.eclipse.jface.action.IAction#run() + */ + @Override + public void run() + { + OfflineView.checkWindow(); + + // call the appropriate delete function + if (deleteAll) + view.deleteAllOfflineReports(); + else + view.deleteSelectedOfflineReports(); + OfflineView.updateActionEnablement(); + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/offlineReports/OfflineReportsFile.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/offlineReports/OfflineReportsFile.java new file mode 100644 index 000000000..8ab8fa48c --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/offlineReports/OfflineReportsFile.java @@ -0,0 +1,278 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.offlineReports; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.mylar.bugzilla.BugzillaPlugin; +import org.eclipse.mylar.bugzilla.core.IBugzillaBug; + + +/** + * Class to persist the data for the offline reports list + */ +public class OfflineReportsFile +{ + /** The file that the offline reports are written to */ + private File file; + + /** The directory to where the file is located */ + /** A list of offline reports */ + private ArrayList<IBugzillaBug> list = new ArrayList<IBugzillaBug>(); + + + /** Sort by bug ID */ + public static final int ID_SORT = 0; + + /** Sort by bug type (locally created or not) */ + public static final int TYPE_SORT = 4; + + /** Default sort by bug TYPE */ + public static int lastSel = TYPE_SORT; + + /** The bug id of the most recently created offline report. */ + protected int latestNewBugId = 0; + + /** + * Constructor that reads the offline reports data persisted in the plugin's state + * directory, if it exists. + * + * @param file + * The file where the offline reports are persisted + * @throws IOException + * Error opening or closing the offline reports file + * @throws ClassNotFoundException + * Error deserializing objects from the offline reports file + */ + public OfflineReportsFile(File file) throws ClassNotFoundException, IOException { + this.file = file; + if (file.exists()) { + readFile(); + } + } + + /** + * Add an offline report to the offline reports list + * @param entry The bug to add + */ + public void add(IBugzillaBug entry) { + // add the entry to the list and write the file to disk + list.add(entry); + writeFile(); + } + + /** + * Updates the offline reports list. + * Used when existing offline reports are modified and saved. + */ + public void update() { + writeFile(); + } + + /** + * @return The id that a new offline bug should use. The value changes each + * time this method is called. + */ + public int getNextOfflineBugId() { + latestNewBugId++; + return latestNewBugId; + } + + /** + * Find a bug in the offline reports list. + * + * @param id + * The bug id that we are looking for + * @return The index of the bug in the array if it exists, else -1. Locally + * created bugs are ignored. + */ + public int find(int id) { + for (int i = 0; i < list.size(); i++) { + IBugzillaBug currBug = list.get(i); + if (currBug != null && (currBug.getId() == id) && !currBug.isLocallyCreated()) + return i; + } + return -1; + } + + /** + * Get the list of offline reports + * @return The list of offline reports + */ + public ArrayList<IBugzillaBug> elements() { + return list; + } + + /** + * Write the offline reports to disk + */ + private void writeFile() { + try { + ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file)); + + // Write the size of the list so that we can read it back in easier + out.writeInt(list.size()); + + out.writeInt(latestNewBugId); + + // write each element in the array list + for (int i = 0; i < list.size(); i++) { + Object item = list.get(i); + out.writeObject(item); + } + out.close(); + } + catch (IOException e) { + // put up a message and log the error if there is a problem writing to the file + MessageDialog.openError(null, + "I/O Error", + "Bugzilla could not write to offline reports file."); + BugzillaPlugin.log(e); + } + } + + /** + * Read the offline reports in from the file on disk + * + * @throws IOException + * Error opening or closing the offline reports file + * @throws ClassNotFoundException + * Error deserializing objects from the offline reports file + */ + private void readFile() throws ClassNotFoundException, IOException { + ObjectInputStream in = new ObjectInputStream(new FileInputStream(file)); + + // get the number of offline reports in the file + int size = in.readInt(); + + // get the bug id of the most recently created offline report + latestNewBugId = in.readInt(); + + // read in each of the offline reports in the file + for (int nX = 0; nX < size; nX++) { + IBugzillaBug item = (IBugzillaBug) in.readObject(); + // add the offline report to the offlineReports list + list.add(item); + } + in.close(); + + sort(lastSel); + } + + /** + * Remove some bugs from the offline reports list + * @param indicesToRemove An array of the indicies of the bugs to be removed + */ + public void remove(List<IBugzillaBug> sel) { + list.removeAll(sel); + + // rewrite the file so that the data is persistant + writeFile(); + } + + /** + * Remove all of the items in the offline reports menu + */ + public void removeAll() { + list.clear(); + + // rewrite the file so that the data is persistant + writeFile(); + } + + /** + * Function to sort the offline reports list + * @param sortOrder The way to sort the bugs in the offline reports list + */ + public void sort(int sortOrder) { + IBugzillaBug[] a = list.toArray(new IBugzillaBug[list.size()]); + + // decide which sorting method to use and sort the offline reports + switch(sortOrder) { + case ID_SORT: + Arrays.sort(a, new SortID()); + lastSel = ID_SORT; + break; + case TYPE_SORT: + Arrays.sort(a, new SortType()); + lastSel = TYPE_SORT; + break; + } + + // remove all of the elements from the list so that we can re-add + // them in a sorted order + list.clear(); + + // add the sorted elements to the list and the table + for (int j = 0; j < a.length; j++) { + add(a[j]); + } + } + + /** + * Inner class to sort by bug id + */ + private class SortID implements Comparator<IBugzillaBug> { + public int compare(IBugzillaBug f1, IBugzillaBug f2) { + Integer id1 = f1.getId(); + Integer id2 = f2.getId(); + + if(id1 != null && id2 != null) + return id1.compareTo(id2); + else if(id1 == null && id2 != null) + return -1; + else if(id1 != null && id2 == null) + return 1; + else + return 0; + } + } + + /** + * Inner class to sort by bug type (locally created or from the server) + */ + private class SortType implements Comparator<IBugzillaBug> { + public int compare(IBugzillaBug f1, IBugzillaBug f2) { + boolean isLocal1 = f1.isLocallyCreated(); + boolean isLocal2 = f2.isLocallyCreated(); + + if (isLocal1 && !isLocal2) { + return -1; + } + else if (!isLocal1 && isLocal2) { + return 1; + } + + // If they are both the same type, sort by ID + Integer id1 = f1.getId(); + Integer id2 = f2.getId(); + + if(id1 != null && id2 != null) + return id1.compareTo(id2); + else if(id1 == null && id2 != null) + return -1; + else if(id1 != null && id2 == null) + return 1; + else + return 0; + } + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/offlineReports/ViewOfflineReportAction.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/offlineReports/ViewOfflineReportAction.java new file mode 100644 index 000000000..91d7297d9 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/offlineReports/ViewOfflineReportAction.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.offlineReports; + +import java.util.Iterator; +import java.util.List; + +import org.eclipse.mylar.bugzilla.BugzillaPlugin; +import org.eclipse.mylar.bugzilla.IBugzillaConstants; +import org.eclipse.mylar.bugzilla.core.BugReport; +import org.eclipse.mylar.bugzilla.core.IBugzillaBug; +import org.eclipse.mylar.bugzilla.ui.OfflineView; +import org.eclipse.mylar.bugzilla.ui.editor.ExistingBugEditorInput; +import org.eclipse.mylar.bugzilla.ui.editor.NewBugEditorInput; +import org.eclipse.mylar.bugzilla.ui.wizard.NewBugModel; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.PartInitException; + + +/** + * View a bug from the offlineReports menu + */ +public class ViewOfflineReportAction extends AbstractOfflineReportsAction +{ + + /** The view to get the result to launch a viewer on */ + private OfflineView view; + + /** + * Constructor + * @param resultsView The view to launch a viewer on + */ + public ViewOfflineReportAction(OfflineView resultsView ) + { + setToolTipText( "View selected offline reports" ); + setText( "View selected" ); + setIcon( "Icons/openresult.gif" ); + view = resultsView; + } + + /** + * View the selected bugs in the editor window + * @see org.eclipse.jface.action.IAction#run() + */ + @Override + public void run() + { + OfflineView.checkWindow(); + List<IBugzillaBug> selectedBugs = view.getSelectedBugs(); + + // if there are some selected bugs view the bugs in the editor window + if (!selectedBugs.isEmpty()) + { + IWorkbenchPage page = BugzillaPlugin.getDefault().getWorkbench().getActiveWorkbenchWindow().getActivePage(); + for (Iterator<IBugzillaBug> it = selectedBugs.iterator(); it.hasNext(); ) { + IBugzillaBug bug = it.next(); + if (bug instanceof BugReport) { + ExistingBugEditorInput editorInput = new ExistingBugEditorInput((BugReport)bug); + try { + page.openEditor(editorInput, IBugzillaConstants.EXISTING_BUG_EDITOR_ID); + } catch (PartInitException e) { + BugzillaPlugin.log(e); + } + continue; + } + if (bug instanceof NewBugModel) { + NewBugEditorInput editorInput = new NewBugEditorInput((NewBugModel)bug); + try { + page.openEditor(editorInput, IBugzillaConstants.NEW_BUG_EDITOR_ID); + } catch (PartInitException e) { + BugzillaPlugin.log(e); + } + continue; + } + } + } + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/saveQuery/GetQueryDialog.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/saveQuery/GetQueryDialog.java new file mode 100644 index 000000000..5531a6e02 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/saveQuery/GetQueryDialog.java @@ -0,0 +1,203 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.saveQuery; + +import java.util.ArrayList; +import java.util.Iterator; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.mylar.bugzilla.search.BugzillaSearchPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.List; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.Shell; + + +/** + * Dialog to display, manage and run stored queries. + */ +public class GetQueryDialog extends Dialog +{ + + /** The Ok button. */ + private Button okButton; + + /** The title of the dialog. */ + private String title; + + private SavedQueryFile input; + + public GetQueryDialog(Shell parentShell, String dialogTitle, SavedQueryFile in) { + super(parentShell); + this.title = dialogTitle; + input = in; + setShellStyle(SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL); + } + + @Override + protected void configureShell(Shell shell) { + super.configureShell(shell); + shell.setText(title); + } + + @Override + protected void createButtonsForButtonBar(Composite parent) { + // create OK and Details buttons + okButton = createButton(parent, IDialogConstants.OK_ID, "Run", true); + okButton.setEnabled(false); + createButton(parent, IDialogConstants.CANCEL_ID, "Close", false); + } + + /** + * Creates the list widget to display stored queries. + */ + @Override + final protected Control createDialogArea(Composite parent) { + Composite composite = (Composite)super.createDialogArea(parent); + + createMainDialogArea(composite); + + return composite; + } + + protected void createMainDialogArea(Composite parent) { + Label label = new Label(parent, SWT.LEFT); + label.setText("Select a saved query:"); + rememberPattern = new List(parent, SWT.SINGLE | SWT.V_SCROLL | SWT.BORDER); + ArrayList<String> names = input.getNames(); + int pos = 0; + + for (Iterator<String> it = names.iterator(); it.hasNext(); ) { + rememberPattern.add(it.next(), pos); + pos++; + } + + GridData gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 5; + gd.heightHint = 60; + + rememberPattern.setLayoutData(gd); + rememberPattern.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + selIndex = rememberPattern.getSelectionIndex(); + okButton.setEnabled(selIndex >= 0); + } + }); + rememberPattern.addMouseListener(new MouseAdapter() { + @Override + public void mouseDoubleClick(MouseEvent e) { + okPressed(); + } + }); + + // Configure the context menu + MenuManager menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$ + menuMgr.add(new RunQueryAction("&Run query")); + menuMgr.add(new Separator()); + menuMgr.add(new RemoveAction("Remo&ve")); + menuMgr.add(new RemoveAllAction("Remove &all")); + Menu menu = menuMgr.createContextMenu(rememberPattern); + rememberPattern.setMenu(menu); + + } + + final protected void setPageComplete(boolean complete) { + if (okButton != null) { + okButton.setEnabled(complete); + } + } + + private String queryNameText; + + private List rememberPattern; + + public String getText() { + return queryNameText; + } + + /** + * Deletes a selected named query. + */ + private void remove() { + int index = rememberPattern.getSelectionIndex(); + if(index != -1) + BugzillaSearchPage.getInput().remove(new int []{index}); + rememberPattern.remove(index); + rememberPattern.setSelection(-1); + selIndex = -1; + okButton.setEnabled(false); + } + + private void removeAll() { + BugzillaSearchPage.getInput().removeAll(); + rememberPattern.removeAll(); + rememberPattern.setSelection(-1); + selIndex = -1; + okButton.setEnabled(false); + } + + /** Index of the selected query, or -1 if none. */ + int selIndex = -1; + + /** + * Returns index of the selected query or -1 if none are selected. + */ + public int getSelected() { + return selIndex; + } + + private class RunQueryAction extends Action { + RunQueryAction(String text) { + super(text); + } + + @Override + public void run() { + GetQueryDialog.this.okPressed(); + } + } + + private class RemoveAction extends Action { + RemoveAction(String text) { + super(text); + } + + @Override + public void run() { + GetQueryDialog.this.remove(); + } + } + + private class RemoveAllAction extends Action { + RemoveAllAction(String text) { + super(text); + } + + @Override + public void run() { + GetQueryDialog.this.removeAll(); + } + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/saveQuery/SaveQueryDialog.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/saveQuery/SaveQueryDialog.java new file mode 100644 index 000000000..75a8ce3cd --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/saveQuery/SaveQueryDialog.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.saveQuery; + +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** + * Dialog for naming a saved query. + */ +public class SaveQueryDialog extends Dialog +{ + private Text queryName; + + /** + * The Ok button. + */ + private Button okButton; + + /** + * The title of the dialog. + */ + private String title; + + public SaveQueryDialog(Shell parentShell, String dialogTitle) { + super(parentShell); + this.title = dialogTitle; + setShellStyle(SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL); + } + + @Override + protected void configureShell(Shell shell) { + super.configureShell(shell); + shell.setText(title); + } + + @Override + protected void createButtonsForButtonBar(Composite parent) { + // create OK and cancel buttons + okButton = createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true); + createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false); + okButton.setEnabled(false); + } + + @Override + protected Control createDialogArea(Composite parent) { + // create composite + Composite composite = (Composite)super.createDialogArea(parent); + + createMainDialogArea(composite); + return composite; + } + + protected void createMainDialogArea(Composite parent) { + queryName = new Text(parent, SWT.SINGLE | SWT.BORDER); + queryName.setLayoutData(new GridData(GridData.BEGINNING | GridData.FILL_HORIZONTAL | GridData.GRAB_HORIZONTAL)); + queryName.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + if (queryName.getText().compareTo("") != 0 && queryName.getText() != null) + okButton.setEnabled(true); + else + okButton.setEnabled(false); + queryNameText = queryName.getText(); + } + }); + } + + String queryNameText; + + public String getText() { + return queryNameText; + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/saveQuery/SavedQueryFile.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/saveQuery/SavedQueryFile.java new file mode 100644 index 000000000..4efa11d6a --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/saveQuery/SavedQueryFile.java @@ -0,0 +1,249 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.saveQuery; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; + +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.mylar.bugzilla.BugzillaPlugin; + + +/** + * This class manages accessing and persisting named Bugzilla queries. + */ +public class SavedQueryFile +{ + /** The file that the queries are written to */ + private File file; + + /** The directory to where the file is located */ + private File directory; + + /** A list of remembered queries */ + private ArrayList<String> list = new ArrayList<String>(); + private ArrayList<String> nameList = new ArrayList<String>(); + private ArrayList<String> sumList = new ArrayList<String>(); + + /** + * Constructor + * @param dirPath The path to the directory where the favorites file should be written to + * @param fileName The file that the favorites should be written to + */ + public SavedQueryFile(String dirPath, String fileName) + { + // create a new file and if it exists, read the data from the file + // else create the file and directories if they dont exist + file = new File(dirPath + fileName); + if (file.exists()) { + readFile(); + } + else { + directory = new File(dirPath); + if (!directory.exists()) + directory.mkdirs(); + writeFile(); + } + } + + /** + * Add a query to the list + * @param entry The query to add + */ + public int add(String entry, String name, String sum) + { + // add the entry to the list and write the file to disk + int index = find(name); + if (index == -1) { + list.add(entry); + nameList.add(name); + sumList.add(sum); + writeFile(); + } + else { + boolean isDuplicate = MessageDialog.openConfirm(BugzillaPlugin.getDefault().getWorkbench().getActiveWorkbenchWindow().getShell(), + "Save Bugzilla Query", + name + " already exists. Do you want to replace it?"); + if (isDuplicate) { + list.add(index, entry); + nameList.add(index,name); + sumList.add(index,sum); + list.remove(index+1); + nameList.remove(index+1); + sumList.remove(index+1); + writeFile(); + } + } + + index = find(entry); + return index; + } + + + /** + * Find a bug in the query list + * @param name The name of the query that we are looking for + * @return The index of the query in the array if it exists, else -1 + */ + public int find(String name) + { + for (int i = 0; i < list.size(); i++) { + String str = nameList.get(i); + if (name.compareTo(str) == 0) + return i; + } + return -1; + } + + /** + * Get the list of queries + * @return The list of queries + */ + public ArrayList<String> elements() + { + return list; + } + + /** + * Write the queries to disk + */ + private void writeFile() + { + try { + OutputStream os = new FileOutputStream(file); + DataOutputStream data = new DataOutputStream(os); + + // Write the size of the list so that we can read it back in easier + data.writeInt(list.size()); + + // write each element in the array list + for (int i = 0; i < list.size(); i++) { + String item = list.get(i); + String itemName = nameList.get(i); + String summary = sumList.get(i); + + // write the string in a machine independant manner + data.writeUTF(item); + data.writeUTF(itemName); + data.writeUTF(summary); + } + + // close the file + data.close(); + } + catch (IOException e) { + // put up a message and log the error if there is a problem writing to the file + BugzillaPlugin.getDefault().logAndShowExceptionDetailsDialog(e, "occurred while saving your Bugzilla queries", "I/O Error"); + } + } + + /** + * Read the queries in from the file on disk + */ + private void readFile() + { + try { + InputStream is = new FileInputStream(file); + DataInputStream data = new DataInputStream(is); + + // get the number of favorites in the file + int size = data.readInt(); + + // read in each of the favorites in the file + for (int nX = 0; nX < size; nX++) { + String item, name, summary; + + // get the data from disk in a machine independant way + item = data.readUTF(); + name = data.readUTF(); + summary = data.readUTF(); + + // add the favorite to the favorites list + list.add(item); + nameList.add(name); + sumList.add(summary); + } + + // close the input stream + data.close(); + } + catch (IOException e) { + // put up a message and log the error if there is a problem reading from the file + BugzillaPlugin.getDefault().logAndShowExceptionDetailsDialog(e, "occurred while restoring saved Bugzilla queries.", "I/O Error"); + } + } + + /** + * Remove some queries from the list + * @param indicesToRemove An array of the indicies of the queries to be removed + */ + public void remove(int[] indicesToRemove) + { + int timesShifted = 0; + + // remove each of the indicated items from the array + for (int i = 0; i < indicesToRemove.length; i++) { + list.remove(indicesToRemove[i] - timesShifted); + nameList.remove(indicesToRemove[i] - timesShifted); + sumList.remove(indicesToRemove[i] - timesShifted); + timesShifted++; + } + + // rewrite the file so that the data is persistant + writeFile(); + + // remove the items from the combo box + timesShifted = 0; + } + + /** + * Remove all of the items in the favortes menu + */ + public void removeAll() { + // remove every element from the favorites list + while (list.size() > 0) { + list.remove(0); + nameList.remove(0); + sumList.remove(0); + } + + // rewrite the file so that the data is persistant + writeFile(); + } + + /** + * Get the query parameters of the currently selected remembered query + * @return The query url + */ + public String getQueryParameters(int index) { + return list.get(index); + } + + /** + * Get the summary text of the currently selected remembered query + * @return The summary text + */ + public String getSummaryText(int index) { + return sumList.get(index); + + } + + public ArrayList<String> getNames() { + return nameList; + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaContentProvider.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaContentProvider.java new file mode 100644 index 000000000..89203c97d --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaContentProvider.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.search; + +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.search.ui.text.AbstractTextSearchResult; + +/** + * An abstract implementation of a content provider for a Bugzilla search. + * @see org.eclipse.jface.viewers.IContentProvider + */ +public abstract class BugzillaContentProvider implements IStructuredContentProvider { + + /** An empty array of objects */ + protected final Object[] EMPTY_ARR= new Object[0]; + + /** The Bugzilla search result for this content provider */ + protected AbstractTextSearchResult bugResult; + + public void dispose() { + // nothing to do + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + if (newInput instanceof BugzillaSearchResult) { + initialize((BugzillaSearchResult) newInput); + } + } + + /** + * Initializes the content provider with the given search result. + * @param result The search result to use with this content provider + */ + protected void initialize(AbstractTextSearchResult result) { + bugResult= result; + } + + /** + * This method is called whenever the set of matches for the given elements changes. + * @param updatedElements The array of objects that has to be refreshed + * @see @see org.eclipse.search.ui.text.AbstractTextSearchViewPage#elementsChanged(java.lang.Object[]) + */ + public abstract void elementsChanged(Object[] updatedElements); + + /** + * Clears the viewer. + */ + public abstract void clear(); +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaIdSearchSorter.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaIdSearchSorter.java new file mode 100644 index 000000000..176e786ca --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaIdSearchSorter.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.search; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerSorter; +import org.eclipse.mylar.bugzilla.IBugzillaConstants; + + +/** + * Sorts results of Bugzilla search by bug id. + */ +public class BugzillaIdSearchSorter extends ViewerSorter { + + /** + * Returns a negative, zero, or positive number depending on whether the + * first bug's id is less than, equal to, or greater than the second bug's + * id. + * <p> + * + * @see org.eclipse.jface.viewers.ViewerSorter#compare(org.eclipse.jface.viewers.Viewer, + * java.lang.Object, java.lang.Object) + */ + @Override + public int compare(Viewer viewer, Object e1, Object e2) { + try { + // cast the object and get its bug id + IMarker entry1 = (IMarker) e1; + Integer id1 = (Integer) entry1.getAttribute( + IBugzillaConstants.HIT_MARKER_ATTR_ID); + + // cast the other object and get its bug id + IMarker entry2 = (IMarker) e2; + Integer id2 = (Integer) entry2.getAttribute( + IBugzillaConstants.HIT_MARKER_ATTR_ID); + + // if neither is null, compare the bug id's + if (id1 != null && id2 != null) { + return id1.compareTo(id2); + } + } + catch (Exception ignored) { + // ignore if there is a problem + } + + // if that didn't work, use the default compare method + return super.compare(viewer, e1, e2); + } + + /** + * Returns the category of the given element. The category is a number used + * to allocate elements to bins; the bins are arranged in ascending numeric + * order. The elements within a bin are arranged via a second level sort + * criterion. + * <p> + * + * @see org.eclipse.jface.viewers.ViewerSorter#category(Object) + */ + @Override + public int category(Object element) { + try { + IMarker marker = (IMarker) element; + + // return the bugs id + if (marker.getType().equals(IBugzillaConstants.HIT_MARKER_ID)) { + return ((Integer)marker.getAttribute(IBugzillaConstants.HIT_MARKER_ATTR_ID)).intValue(); + } + } + catch (Exception ignored) { + // ignore if there is a problem + } + + // if that didn't work, use the default category method + return super.category(element); + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaLabelProvider.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaLabelProvider.java new file mode 100644 index 000000000..f117ade15 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaLabelProvider.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.search; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.mylar.bugzilla.IBugzillaConstants; + +/** + * Label provider for Bugzilla search items. + */ +public class BugzillaLabelProvider extends LabelProvider +{ + /** A list of the default severity labels */ + private static final String [] severityLabel = {"blocker", "critical", "major", "normal", "minor", "trivial", "enhancement"}; + + /** A list of the default priority labels */ + private static final String [] priorityLabel = {"P1", "P2", "P3", "P4", "P5", "--"}; + + /** A list of the default state labels */ + private static final String [] stateLabel = {"Unconfirmed", "New", "Assigned", "Reopened", "Resolved", "Verified", "Closed"}; + + /** A list of the default result labels */ + private static final String [] resultLabel = {"", "fixed", "invalid", "wont fix", "later", "remind", "duplicate", "works for me"}; + + /** + * Returns the text for the label of the given element. + * + * @see org.eclipse.jface.viewers.ILabelProvider#getText(java.lang.Object) + */ + @Override + public String getText(Object element) { + if (element instanceof IMarker) { + + try { + IMarker marker = (IMarker) element; + + // get the severity of the bug + String severity = severityLabel[((Integer) marker + .getAttribute(IBugzillaConstants.HIT_MARKER_ATTR_SEVERITY)) + .intValue()]; + + // get the priority of the bug + String priority = priorityLabel[((Integer) marker + .getAttribute(IBugzillaConstants.HIT_MARKER_ATTR_PRIORITY)) + .intValue()]; + + // get the state of the bug + String state = stateLabel[((Integer) marker + .getAttribute(IBugzillaConstants.HIT_MARKER_ATTR_STATE)) + .intValue()]; + + // get the resolution of the bug + String result = resultLabel[((Integer) marker + .getAttribute(IBugzillaConstants.HIT_MARKER_ATTR_RESULT)) + .intValue()]; + + // return a string containing the information about the bug to + // be displayed + // in the searh window + return "Bug " + + marker.getAttribute(IBugzillaConstants.HIT_MARKER_ATTR_ID) + + " (" + + severity + + " - " + + priority + + " - " + + state + + (result.length() > 0 ? " " + result : "") + + ") " + + " - " + + marker.getAttribute(IBugzillaConstants.HIT_MARKER_ATTR_DESC) + + " (" + + marker.getAttribute(IBugzillaConstants.HIT_MARKER_ATTR_OWNER) + + ") "; + } + catch (CoreException ignored) { + // ignore if there is a problem + } + } + + // return an empty string if there is a problem + return ""; + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaPrioritySearchSorter.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaPrioritySearchSorter.java new file mode 100644 index 000000000..2f5d789e0 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaPrioritySearchSorter.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.search; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerSorter; +import org.eclipse.mylar.bugzilla.IBugzillaConstants; + + +/** + * Sorts results of Bugzilla search by bug priority. + */ +public class BugzillaPrioritySearchSorter extends ViewerSorter { + + /** + * Returns a negative, zero, or positive number depending on whether the + * first bug's priority goes before, is the same as, or goes after the + * second element's priority. + * <p> + * + * @see org.eclipse.jface.viewers.ViewerSorter#compare(org.eclipse.jface.viewers.Viewer, + * java.lang.Object, java.lang.Object) + */ + @Override + public int compare(Viewer viewer, Object e1, Object e2) { + try { + // cast the object and get the bugs priority + IMarker entry1 = (IMarker) e1; + Integer pr1 = (Integer) entry1.getAttribute( + IBugzillaConstants.HIT_MARKER_ATTR_PRIORITY); + + // cast the other object and get the bugs priority + IMarker entry2 = (IMarker) e2; + Integer pr2 = (Integer) entry2.getAttribute( + IBugzillaConstants.HIT_MARKER_ATTR_PRIORITY); + + // if neither is null, compare the bug priority + if (pr1 != null && pr2 != null) { + return pr1.compareTo(pr2); + } + } + catch (Exception ignored) { + // do nothing + } + + // if that didn't work, use the default compare method + return super.compare(viewer, e1, e2); + } + + /** + * Returns the category of the given element. The category is a number used + * to allocate elements to bins; the bins are arranged in ascending numeric + * order. The elements within a bin are arranged via a second level sort + * criterion. + * <p> + * + * @see org.eclipse.jface.viewers.ViewerSorter#category(Object) + */ + @Override + public int category(Object element) { + try { + IMarker marker = (IMarker) element; + + // return the bugs id + if (marker.getType().equals(IBugzillaConstants.HIT_MARKER_ID)) { + return ((Integer)marker.getAttribute(IBugzillaConstants.HIT_MARKER_ATTR_ID)).intValue(); + } + } + catch (Exception ignored) { + // ignore if there is a problem + } + + // if that didn't work, use the default category method + return super.category(element); + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaQueryPageParser.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaQueryPageParser.java new file mode 100644 index 000000000..32ab91576 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaQueryPageParser.java @@ -0,0 +1,513 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.search; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.text.ParseException; +import java.util.ArrayList; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.security.auth.login.LoginException; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.mylar.bugzilla.BugzillaPlugin; +import org.eclipse.mylar.bugzilla.BugzillaPreferences; +import org.eclipse.mylar.bugzilla.IBugzillaConstants; +import org.eclipse.mylar.bugzilla.TrustAll; +import org.eclipse.mylar.bugzilla.core.internal.HtmlStreamTokenizer; +import org.eclipse.mylar.bugzilla.core.internal.HtmlTag; +import org.eclipse.mylar.bugzilla.core.internal.HtmlStreamTokenizer.Token; + + +/** + * Class to parse the update data from the server + * + * author: kvesik + * + * created on: Feb 25, 2003 + * + */ +public class BugzillaQueryPageParser +{ + /** The name of the bugzilla server */ + private String urlString; + + /** The input stream */ + private BufferedReader in = null; + + /** True if the operation was successful */ + private boolean successful; + + /** Exception to be displayed if there was an error*/ + private Exception exception; + + /** The progress monitor for the update */ + private IProgressMonitor monitor; + + /** Selection lists as ArrayLists */ + private ArrayList<String> statusValues = new ArrayList<String>(); + private ArrayList<String> preselectedStatusValues = new ArrayList<String>(); + private ArrayList<String> resolutionValues = new ArrayList<String>(); + private ArrayList<String> severityValues = new ArrayList<String>(); + private ArrayList<String> priorityValues = new ArrayList<String>(); + private ArrayList<String> hardwareValues = new ArrayList<String>(); + private ArrayList<String> osValues = new ArrayList<String>(); + private ArrayList<String> productValues = new ArrayList<String>(); + private ArrayList<String> componentValues = new ArrayList<String>(); + private ArrayList<String> versionValues = new ArrayList<String>(); + private ArrayList<String> targetValues = new ArrayList<String>(); + + /** + * Constructor + * @param monitor The progress monitor for the update + */ + public BugzillaQueryPageParser(IProgressMonitor monitor) throws LoginException + { + this.monitor = monitor; + + + // get the servers url + urlString = BugzillaPlugin.getDefault().getServerName() + "/query.cgi"; + + // if we are dealing with 2.18 we need to use the folowing in the + // query string to get the right search page + if(BugzillaPreferences.is218()){ + urlString += "?format=advanced"; + } + + // use the user name and password if we have it + if(BugzillaPreferences.getUserName() != null && !BugzillaPreferences.getUserName().equals("") && BugzillaPreferences.getPassword() != null && !BugzillaPreferences.getPassword().equals("")) + { + try { + // if we are dealing with 2.18 we already have the ? from before so we need + // an & instead. If other version, still add ? + if(BugzillaPreferences.is218()) + urlString+="&"; + else + urlString+="?"; + + urlString += "GoAheadAndLogIn=1&Bugzilla_login=" + URLEncoder.encode(BugzillaPreferences.getUserName(), "UTF-8") + "&Bugzilla_password=" + URLEncoder.encode(BugzillaPreferences.getPassword(), "UTF-8"); + } + catch (UnsupportedEncodingException e) { + /* + * Do nothing. Every implementation of the Java platform is required + * to support the standard charset "UTF-8" + */ + } + } + + successful = false; + + // try to get the new options from the page + parseDocument(); + if (!successful) { + if (exception instanceof MalformedURLException) { + MessageDialog.openError(null, "Unsupported Protocol", "The server that was specified for Bugzilla is not supported by your JVM.\nPlease make sure that you are using a JDK that supports SSL."); + } + else { + // if there was a problem with the operation, display an error message + ErrorDialog.openError( null, + "Incomplete operation", + "Bugzilla could not complete the the update.", + new Status( IStatus.ERROR, + IBugzillaConstants.PLUGIN_ID, + IStatus.OK, + exception.getMessage(), + exception)); + } + } + } + + /** + * Get whether the update was successful + * @return <code>true</code> if the update was successful + */ + public boolean wasSuccessful() + { + return successful; + } + + /** + * Parse the data from the server for the query options + */ + private void parseDocument() throws LoginException { + try { + // if the operation has been cancelled already, return + if (monitor.isCanceled()) { + monitor.done(); + return; + } + + // try to connect to the server + monitor.subTask("Connecting to server"); + + SSLContext ctx = SSLContext.getInstance("TLS"); + + javax.net.ssl.TrustManager[] tm = new javax.net.ssl.TrustManager[]{new TrustAll()}; + ctx.init(null, tm, null); + HttpsURLConnection.setDefaultSSLSocketFactory(ctx.getSocketFactory()); + monitor.worked(1); + + URL url = new URL(this.urlString); + + // initialize the input stream + in = new BufferedReader(new InputStreamReader(url.openStream())); + + // increment the position of the status monitor + monitor.worked(2); + + // check if the operation has been cancelled so we can end if it has been + if (monitor.isCanceled()) + monitor.done(); + else + monitor.subTask("Reading values from server"); + + // parse the data from the server + parseQueryPage(in); + + // set the operation to being successful + successful = true; + + } + catch (LoginException e) { + throw e; + } + catch (Exception e) { + // if we can't connect, log the problem and save the exception to handle later + monitor.done(); + exception = e; + BugzillaPlugin.log(new Status( IStatus.ERROR, + IBugzillaConstants.PLUGIN_ID, + IStatus.OK, + "Failed to create URL and open input stream: " + urlString, + e)); + return; + } + finally { + try { + if (in != null) + in.close(); + } catch (IOException exitAnyway) { + in = null; + } + } + } + + /** + * Check if all of the lists of options are empty + * @return true if all of the options lists are empty + */ + private boolean allListsEmpty() { + return statusValues.isEmpty() && preselectedStatusValues.isEmpty() + && resolutionValues.isEmpty() && severityValues.isEmpty() + && priorityValues.isEmpty() && hardwareValues.isEmpty() + && osValues.isEmpty() && productValues.isEmpty() + && componentValues.isEmpty() && versionValues.isEmpty() + && targetValues.isEmpty(); + } + + /** + * Get the new status values + * @return An array of the new status values + */ + public String[] getStatusValues() { + String[] array = new String[statusValues.size()]; + + // create the array and return it + for (int i = 0; i < statusValues.size(); i++) + array[i] = statusValues.get(i); + return array; + } + + /** + * Get the new preselected status values + * @return An array of the new preselected status values + */ + public String[] getPreselectedStatusValues() { + String[] array = new String[preselectedStatusValues.size()]; + + // create the array and return it + for (int i = 0; i < preselectedStatusValues.size(); i++) + array[i] = preselectedStatusValues.get(i); + return array; + } + + /** + * Get the new resolution values + * @return An array of the new resolution values + */ + public String[] getResolutionValues() { + String[] array = new String[resolutionValues.size()]; + + // create the array and return it + for (int i = 0; i < resolutionValues.size(); i++) + array[i] = resolutionValues.get(i); + return array; + } + + /** + * Get the new severity values + * @return An array of the new severity values + */ + public String[] getSeverityValues() { + String[] array = new String[severityValues.size()]; + + // create the array and return it + for (int i = 0; i < severityValues.size(); i++) + array[i] = severityValues.get(i); + return array; + } + + /** + * Get the new priority values + * @return An array of the new priority values + */ + public String[] getPriorityValues() { + String[] array = new String[priorityValues.size()]; + + // create the array and return it + for (int i = 0; i < priorityValues.size(); i++) + array[i] = priorityValues.get(i); + return array; + } + + /** + * Get the new hardware values + * @return An array of the new hardware values + */ + public String[] getHardwareValues() { + String[] array = new String[hardwareValues.size()]; + + // create the array and return it + for (int i = 0; i < hardwareValues.size(); i++) + array[i] = hardwareValues.get(i); + return array; + } + + /** + * Get the new OS values + * @return An array of the new OS values + */ + public String[] getOSValues() { + String[] array = new String[osValues.size()]; + + // create the array and return it + for (int i = 0; i < osValues.size(); i++) + array[i] = osValues.get(i); + return array; + } + + /** + * Get the new product values + * @return An array of the new product values + */ + public String[] getProductValues() { + String[] array = new String[productValues.size()]; + + // create the array and return it + for (int i = 0; i < productValues.size(); i++) + array[i] = productValues.get(i); + return array; + } + + /** + * Get the new component values + * @return An array of the new component values + */ + public String[] getComponentValues() { + String[] array = new String[componentValues.size()]; + + // create the array and return it + for (int i = 0; i < componentValues.size(); i++) + array[i] = componentValues.get(i); + return array; + } + + /** + * Get the new version values + * @return An array of the new version values + */ + public String[] getVersionValues() { + String[] array = new String[versionValues.size()]; + + // create the array and return it + for (int i = 0; i < versionValues.size(); i++) + array[i] = versionValues.get(i); + return array; + } + + /** + * Get the new milestone values + * @return An array of the new milestone values + */ + public String[] getTargetValues() { + String[] array = new String[targetValues.size()]; + + // create the array and return it + for (int i = 0; i < targetValues.size(); i++) + array[i] = targetValues.get(i); + return array; + } + + /** + * Parse the bugzilla query.cgi page for some seach options + * @param inputReader The input stream for the page + * @throws LoginException + * @throws ParseException + * @throws IOException + */ + private void parseQueryPage(Reader inputReader) throws LoginException, ParseException, IOException + { + HtmlStreamTokenizer tokenizer = new HtmlStreamTokenizer(inputReader, null); + + boolean isTitle = false; + boolean possibleBadLogin = false; + String title = ""; + + for (HtmlStreamTokenizer.Token token = tokenizer.nextToken(); token.getType() != Token.EOF; token = tokenizer.nextToken()) { + + // make sure that bugzilla doesn't want us to login + if(token.getType() == Token.TAG && ((HtmlTag)(token.getValue())).getTagType() == HtmlTag.Type.TITLE && !((HtmlTag)(token.getValue())).isEndTag()) { + isTitle = true; + continue; + } + + if (isTitle) { + // get all of the data in the title tag to compare with + if (token.getType() != Token.TAG) { + title += ((StringBuffer)token.getValue()).toString().toLowerCase() + " "; + continue; + } + else if (token.getType() == Token.TAG && ((HtmlTag)token.getValue()).getTagType() == HtmlTag.Type.TITLE && ((HtmlTag)token.getValue()).isEndTag()) { + // check if the title looks like we may have a problem with login + if ((title.indexOf("login") != -1 || (title.indexOf("invalid") != -1 && title.indexOf("password") != -1) || title.indexOf("check e-mail") != -1 || title.indexOf("error") != -1)) + possibleBadLogin = true; + isTitle = false; + title = ""; + } + continue; + } + + // we have found the start of attribute values + if (token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.TD && "left".equalsIgnoreCase(tag.getAttribute("align"))) { + // parse the attribute values + parseAttributeValue(tokenizer); + continue; + } + } + } + + // if all of the lists are empty and we suspect bad login info, assume that it was a bad login + if (possibleBadLogin && allListsEmpty()) + throw new LoginException("Bugzilla login information incorrect"); + } + + /** + * Parse the case where the attribute value is an option + * @param parameterName The name of the attribute value + * @param tokenizer The tokenizer to get data from the stream + * @throws IOException + * @throws ParseException + */ + private void parseSelect( + String parameterName, + HtmlStreamTokenizer tokenizer) + throws IOException, ParseException { + + HtmlStreamTokenizer.Token token = tokenizer.nextToken(); + while (token.getType() != Token.EOF) { + if (token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.SELECT && tag.isEndTag()) break; + if (tag.getTagType() == HtmlTag.Type.OPTION && !tag.isEndTag()) { + String optionName = tag.getAttribute("value"); + boolean selected = tag.hasAttribute("selected"); + StringBuffer optionText = new StringBuffer(); + for (token = tokenizer.nextToken(); token.getType() == Token.TEXT; token = tokenizer.nextToken()) { + if (optionText.length() > 0) { + optionText.append(' '); + } + optionText.append((StringBuffer) token.getValue()); + } + // add the value to the appropriate list of attributes + if (parameterName.equals("bug_status")) { + statusValues.add(optionName); + + // check if the status is to be preselected or not + if (selected) + preselectedStatusValues.add(optionName); + } + else if (parameterName.equals("resolution")) + resolutionValues.add(optionName); + else if (parameterName.equals("bug_severity")) + severityValues.add(optionName); + else if (parameterName.equals("priority")) + priorityValues.add(optionName); + else if (parameterName.equals("rep_platform")) + hardwareValues.add(optionName); + else if (parameterName.equals("op_sys")) + osValues.add(optionName); + else if (parameterName.equals("product")) + productValues.add(optionName); + else if (parameterName.equals("component")) + componentValues.add(optionName); + else if (parameterName.equals("version")) + versionValues.add(optionName); + else if (parameterName.equals("target_milestone")) + targetValues.add(optionName); + } + else { + token = tokenizer.nextToken(); + } + } + else { + token = tokenizer.nextToken(); + } + } + } + + /** + * Parse the case where we think we found an attribute value + * @param tokenizer The tokenizer to get the data from the stream + * @throws IOException + * @throws ParseException + */ + private void parseAttributeValue( + HtmlStreamTokenizer tokenizer) + throws IOException, ParseException { + + HtmlStreamTokenizer.Token token = tokenizer.nextToken(); + if (token.getType() == Token.TAG) { + HtmlTag tag = (HtmlTag) token.getValue(); + if (tag.getTagType() == HtmlTag.Type.SELECT && !tag.isEndTag()) { + String parameterName = tag.getAttribute("name"); + parseSelect(parameterName, tokenizer); + } + else if (tag.getTagType() == HtmlTag.Type.LABEL && !tag.isEndTag()) { + parseAttributeValue(tokenizer); + } + } + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchEngine.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchEngine.java new file mode 100644 index 000000000..3bac1592f --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchEngine.java @@ -0,0 +1,293 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.search; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.security.auth.login.LoginException; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.MultiStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.eclipse.mylar.bugzilla.BugzillaPlugin; +import org.eclipse.mylar.bugzilla.BugzillaPreferences; +import org.eclipse.mylar.bugzilla.IBugzillaConstants; +import org.eclipse.mylar.bugzilla.TrustAll; +import org.eclipse.mylar.bugzilla.core.BugzillaException; +import org.eclipse.search.ui.NewSearchUI; + + +import com.sun.org.apache.xerces.internal.impl.xpath.regex.Match; +import com.sun.org.apache.xerces.internal.impl.xpath.regex.RegularExpression; + +/** + * Queries the Bugzilla server for the list of bugs matching search criteria. + */ +public class BugzillaSearchEngine { + + protected static final String QUERYING_SERVER = "Querying Bugzilla Server..."; + + /** regular expression matching Bugzilla query results format used in Eclipse.org Bugzilla */ + protected static final RegularExpression re = new RegularExpression("<a href=\"show_bug.cgi\\?id=(\\d+)\">", "i"); + + /** regular expression matching values of query matches' attributes in Eclipse.org Bugzilla */ + protected static final RegularExpression reValue = new RegularExpression("<td><nobr>([^<]*)</nobr>"); + + /** regular expression matching Bugzilla query results format used in v2.12 */ + protected static final RegularExpression reOld = new RegularExpression("<a href=\"show_bug.cgi\\?id=(\\d+)\">\\d+</a>\\s*<td class=severity><nobr>([^>]+)</nobr><td class=priority><nobr>([^>]+)</nobr><td class=platform><nobr>([^>]*)</nobr><td class=owner><nobr>([^>]*)</nobr><td class=status><nobr>([^>]*)</nobr><td class=resolution><nobr>([^>]*)</nobr><td class=summary>(.*)$", "i"); + + private String urlString; + + public BugzillaSearchEngine(String url) { + this.urlString = url; + + // use the username and password if we have it to log into bugzilla + if(BugzillaPreferences.getUserName() != null && !BugzillaPreferences.getUserName().equals("") && BugzillaPreferences.getPassword() != null && !BugzillaPreferences.getPassword().equals("")) + { + try { + url += "&GoAheadAndLogIn=1&Bugzilla_login=" + URLEncoder.encode(BugzillaPreferences.getUserName(), "UTF-8") + "&Bugzilla_password=" + URLEncoder.encode(BugzillaPreferences.getPassword(), "UTF-8"); + } catch (UnsupportedEncodingException e) { + /* + * Do nothing. Every implementation of the Java platform is required + * to support the standard charset "UTF-8" + */ + } + } + } + + /** + * Wrapper for search + * @param collector - The collector for the results to go into + */ + public IStatus search(IBugzillaSearchResultCollector collector) throws LoginException + { + return this.search(collector, 0); + } + + /** + * Executes the query, parses the response, and adds hits to the search result collector. + * + * <p> + * The output for a single match looks like this: + * <pre> + * <tr class="bz_enhancement bz_P5 "> + * + * <td> + * <a href="show_bug.cgi?id=6747">6747</a> + * </td> + * + * <td><nobr>enh</nobr> + * </td> + * <td><nobr>P5</nobr> + * </td> + * <td><nobr>All</nobr> + * </td> + * <td><nobr>Olivier_Thomann@oti.com</nobr> + * </td> + * <td><nobr>ASSI</nobr> + * </td> + * <td><nobr></nobr> + * </td> + * <td>Code Formatter exchange several blank lines w/ one + * </td> + * + * </tr> + * <pre> + * + * <p>Or in the older format: + * <pre> + * <A HREF="show_bug.cgi?id=8">8</A> <td class=severity><nobr>blo</nobr><td class=priority><nobr>P1</nobr><td class=platform><nobr>PC</nobr><td class=owner><nobr>cubranic@cs.ubc.ca</nobr><td class=status><nobr>CLOS</nobr><td class=resolution><nobr>DUPL</nobr><td class=summary>"Document root" missing when querying on files and revisions + * </pre> + * @param collector - The collector for the search results + * @param startMatches - The number of matches to start with for the progress monitor + */ + public IStatus search(IBugzillaSearchResultCollector collector, int startMatches) throws LoginException { + IProgressMonitor monitor = collector.getProgressMonitor(); + + IStatus status = null; + + boolean possibleBadLogin = false; + int numCollected = 0; + + BufferedReader in = null; + + try { + monitor.beginTask(QUERYING_SERVER, IProgressMonitor.UNKNOWN); + collector.aboutToStart(startMatches); + + SSLContext ctx = SSLContext.getInstance("TLS"); + + javax.net.ssl.TrustManager[] tm = new javax.net.ssl.TrustManager[]{new TrustAll()}; + ctx.init(null, tm, null); + HttpsURLConnection.setDefaultSSLSocketFactory(ctx.getSocketFactory()); + + URL url = new URL(this.urlString); + + HttpURLConnection connect = (HttpURLConnection) url.openConnection(); + connect.connect(); + + int responseCode = connect.getResponseCode(); + + if(responseCode != HttpURLConnection.HTTP_OK) + { + String msg; + if(responseCode == -1 || responseCode == HttpURLConnection.HTTP_FORBIDDEN) + msg = BugzillaPlugin.getDefault().getServerName() + " does not seem to be a valid Bugzilla server. Check Bugzilla preferences."; + else + msg = "HTTP Error " + responseCode + " (" + connect.getResponseMessage() + ") while querying Bugzilla Server. Check Bugzilla preferences."; + + throw new BugzillaException(msg); + } + + in = new BufferedReader(new InputStreamReader(url.openStream())); + + Match match = new Match(); + String line; + while ((line = in.readLine()) != null) { + // create regular expressions that can be mathced to check if we have + // bad login information + RegularExpression loginRe = new RegularExpression("<title>.*login.*</title>.*"); + RegularExpression invalidRe = new RegularExpression(".*<title>.*invalid.*password.*</title>.*"); + RegularExpression passwordRe = new RegularExpression(".*<title>.*password.*invalid.*</title>.*"); + RegularExpression emailRe = new RegularExpression(".*<title>.*check e-mail.*</title>.*"); + RegularExpression errorRe = new RegularExpression(".*<title>.*error.*</title>.*"); + + String lowerLine = line.toLowerCase(); + + // check if we have anything that suggests bad login info + if(loginRe.matches(lowerLine) || invalidRe.matches(lowerLine) || passwordRe.matches(lowerLine) || emailRe.matches(lowerLine) || errorRe.matches(lowerLine)) + possibleBadLogin = true; + + if (reOld.matches(line, match)) { + int id = Integer.parseInt(match.getCapturedText(1)); + String severity = match.getCapturedText(2); + String priority = match.getCapturedText(3); + String platform = match.getCapturedText(4); + String owner = match.getCapturedText(5); + String state = match.getCapturedText(6); + String result = match.getCapturedText(7); + String description = match.getCapturedText(8); + String query = BugzillaPlugin.getMostRecentQuery(); + if (query == null) + query = ""; + + BugzillaSearchHit hit = new BugzillaSearchHit(id, description, severity, priority, platform, state, result, owner, query); + collector.accept(hit); + numCollected++; + } + else if (re.matches(line, match)) { + int id = Integer.parseInt(match.getCapturedText(1)); + String severity = null; + String priority = null; + String platform = null; + String owner = null; + String state = null; + String result = null; + for (int i = 0; i < 6; i++) { + do { + line = in.readLine().trim(); + if (line == null) break; + line = line.trim(); + } while (!reValue.matches(line, match)); + switch (i) { + case 0: + severity = match.getCapturedText(1); + break; + case 1: + priority = match.getCapturedText(1); + break; + case 2: + platform = match.getCapturedText(1); + break; + case 3: + owner = match.getCapturedText(1); + break; + case 4: + state = match.getCapturedText(1); + break; + case 5: + result = match.getCapturedText(1); + break; + } + } + + // two more + line = in.readLine(); + line = in.readLine(); + + String description = line.substring(8); + String query = BugzillaPlugin.getMostRecentQuery(); + if (query == null) + query = ""; + BugzillaSearchHit hit = new BugzillaSearchHit(id, description, severity, priority, platform, state, result, owner, query); + collector.accept(hit); + numCollected++; + } + if (monitor.isCanceled()) { + throw new OperationCanceledException("Search cancelled"); + } + } + }catch (CoreException e) { + status = new MultiStatus( IBugzillaConstants.PLUGIN_ID, IStatus.ERROR, "Core Exception occurred while querying Bugzilla Server " + BugzillaPlugin.getDefault().getServerName() + ".\n" + + "\nClick Details for more information.", e); + ((MultiStatus)status).add(e.getStatus()); + + // write error to log + BugzillaPlugin.log(status); + } + catch (OperationCanceledException e) { + status = new Status(IStatus.CANCEL, IBugzillaConstants.PLUGIN_ID, + IStatus.CANCEL, "", null); + }catch (Exception e) { + status = new MultiStatus( IBugzillaConstants.PLUGIN_ID, IStatus.ERROR, e.getClass().toString() + " occurred while querying Bugzilla Server " + BugzillaPlugin.getDefault().getServerName() + ".\n" + + "\nClick Details or see log for more information.", e); + + IStatus s = new Status(IStatus.ERROR, IBugzillaConstants.PLUGIN_ID, IStatus.ERROR, e.getClass().toString() + ": ", e); + ((MultiStatus)status).add(s); + s = new Status (IStatus.ERROR, IBugzillaConstants.PLUGIN_ID, IStatus.ERROR, e.getMessage(), e); + ((MultiStatus)status).add(s); + + // write error to log + BugzillaPlugin.log(status); + + } finally { + monitor.done(); + collector.done(); + try{ + if(in != null) + in.close(); + }catch(IOException e) + { + BugzillaPlugin.log(new Status(IStatus.ERROR, IBugzillaConstants.PLUGIN_ID,IStatus.ERROR,"Problem closing the stream", e)); + } + } + + // if we haven't collected any serach results and we suspect a bad login, we assume it was a bad login + if(numCollected == 0 && possibleBadLogin) + throw new LoginException("Bugzilla login information incorrect"); + + if(status == null) + return new Status(IStatus.OK, NewSearchUI.PLUGIN_ID, IStatus.OK, "", null); + else + return status; + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchHit.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchHit.java new file mode 100644 index 000000000..fed8ba051 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchHit.java @@ -0,0 +1,341 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.search; + +import java.io.IOException; + +import javax.security.auth.login.LoginException; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.MultiStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.mylar.bugzilla.BugzillaPlugin; +import org.eclipse.mylar.bugzilla.IBugzillaConstants; +import org.eclipse.mylar.bugzilla.ui.editor.ExistingBugEditorInput; +import org.eclipse.search.internal.ui.SearchMessages; +import org.eclipse.search.internal.ui.SearchPlugin; +import org.eclipse.search.internal.ui.util.ExceptionHandler; +import org.eclipse.search.ui.NewSearchUI; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.PartInitException; + + +/** + * An item in the Bugzilla database matching the search criteria. + */ +public class BugzillaSearchHit +{ + /** The bug id */ + private int id; + + /** The description of the bug */ + private String description; + + /** The severity of the bug */ + private String severity; + + /** The priority of the bug */ + private String priority; + + /** The platform that the bug was found in */ + private String platform; + + /** The state of the bug */ + private String state; + + /** The resolution of the bug */ + private String result; + + /** The owner of the bug */ + private String owner; + + /** The query that the bug was a result of */ + private String query; + + /** The editor to use when a bug is opened */ + private static IEditorPart fEditor; + + /** + * Constructor + * @param id The id of the bug + * @param description The description of the bug + * @param severity The severity of the bug + * @param priority The priority of the bug + * @param platform The platform the bug was found in + * @param state The state of the bug + * @param result The resolution of the bug + * @param owner The owner of the bug + * @param query the query that the bug was a result of + */ + public BugzillaSearchHit(int id, String description, String severity, String priority, String platform, String state, String result, String owner, String query) + { + this.id = id; + this.description = description; + this.severity = severity; + this.priority = priority; + this.platform = platform; + this.state = state; + this.result = result; + this.owner = owner; + this.query = query; + } + + /** + * Get the bugs id + * @return The bugs id + */ + public int getId() + { + return id; + } + + /** + * Get the bugs description + * @return The description of the bug + */ + public String getDescription() + { + return description; + } + + /** + * Get the bugs priority + * @return The priority of the bug + */ + public String getPriority() + { + return priority; + } + + /** + * Get the bugs severity + * @return The severity of the bug + */ + public String getSeverity() + { + return severity; + } + + /** + * Get the platform the bug occurred under + * @return The platform that the bug occured under + */ + public String getPlatform() + { + return platform; + } + + /** + * Get the bugs state + * @return The state of the bug + */ + public String getState() + { + return state; + } + + /** + * Get the bugs resolution + * @return The resolution of the bug + */ + public String getResult() + { + return result; + } + + /** + * Get the bugs owner + * @return The owner of the bug + */ + public String getOwner() + { + return owner; + } + + /** + * Get the query that the bug was a result of + * @return The query that the bug was a result of + */ + public String getQuery() + { + return query; + } + + @Override + public String toString() + { + return id + " " + description + "\n"; + } + + + /** + * Convenience method for opening a bug in an editor. + * @param id The bug id of the bug to open in the editor + */ + public static boolean show(int id) + { + // determine if the editor is to be reused or not and call the appropriate + // function to show the bug + if (NewSearchUI.reuseEditor()) + return showWithReuse(id); + else + return showWithoutReuse(id); + } + + /** + * Show the bug in the same editor window + * @param id The id of the bug to show + */ + private static boolean showWithReuse(int id) + { + // get the active page so that we can reuse it + IWorkbenchPage page = SearchPlugin.getActivePage(); + try + { + // if we couldn't get a page, get out + if (page == null) + return true; + + IEditorInput input = null; + + // try to get an editor input on the bug + input = new ExistingBugEditorInput(id); + + // check if we found a valid bug + if(((ExistingBugEditorInput)input).getBug() == null) + { + MessageDialog.openError(null, "No such bug", "No bug exists with this id"); + return false; + } + + // get the editor for the page + IEditorPart editor = page.findEditor(input); + + if (editor == null) + { + // close the current editor if it is clean and open + if (fEditor != null && !fEditor.isDirty()) + page.closeEditor(fEditor, false); + + try + { + // try to open a new editor with the input bug, but don't activate it + editor= page.openEditor(input, IBugzillaConstants.EXISTING_BUG_EDITOR_ID, false); + } + catch (PartInitException ex) + { + // if there was a problem, handle it and log it, then get out of here + ExceptionHandler.handle(ex, SearchMessages.Search_Error_search_title, SearchMessages.Search_Error_search_message); //$NON-NLS-2$ //$NON-NLS-1$ + BugzillaPlugin.log(ex.getStatus()); + return false; + } + + } + else + { + // if a editor is openon that bug, just bring it to the top + // of the editors + page.bringToTop(editor); + } + + if (editor != null) + { + // if we have an editor, save it for later use + fEditor= editor; + } + } + catch(LoginException e) + { + MessageDialog.openError(null, "Login Error", "Bugzilla could not log you in to get the information you requested since login name or password is incorrect.\nPlease check your settings in the bugzilla preferences. "); + BugzillaPlugin.log(e); + } + catch(IOException e){ + IStatus status = new MultiStatus( IBugzillaConstants.PLUGIN_ID, IStatus.ERROR, e.getClass().toString() + " occurred while opening the bug report. \n\nClick Details or see log for more information.", e); + IStatus s = new Status(IStatus.ERROR, IBugzillaConstants.PLUGIN_ID, IStatus.ERROR, e.getClass().toString() + ": ", e); + ((MultiStatus)status).add(s); + s = new Status (IStatus.ERROR, IBugzillaConstants.PLUGIN_ID, IStatus.ERROR, e.getMessage(), e); + ((MultiStatus)status).add(s); + + //write error to log + BugzillaPlugin.log(status); + + ErrorDialog.openError(null, "Bugzilla Error", null, status); + return false; + } + + return true; + } + + /** + * Show the bug in a new editor window + * @param id The id of the bug to show + */ + private static boolean showWithoutReuse(int id) + { + // get the active workbench page + IWorkbenchPage page = SearchPlugin.getActivePage(); + try + { + // if we couldn't get the page, get out of here + if (page == null) + return true; + + IEditorInput input = null; + String editorId = IBugzillaConstants.EXISTING_BUG_EDITOR_ID; + + // get a new editor input on the bug that we want to open + input = new ExistingBugEditorInput(id); + + // check if we found a valid bug + if(((ExistingBugEditorInput)input).getBug() == null) + { + MessageDialog.openError(null, "No such bug", "No bug exists with this id"); + return false; + } + + try + { + // try to open an editor on the input bug + page.openEditor(input, editorId); + } + catch (PartInitException ex) + { + // if we have a problem, handle it, log it, and get out of here + ExceptionHandler.handle(ex, SearchMessages.Search_Error_search_title, SearchMessages.Search_Error_search_message); //$NON-NLS-2$ //$NON-NLS-1$ + BugzillaPlugin.log(ex.getStatus()); + return false; + } + } + catch(LoginException e) + { + MessageDialog.openError(null, "Login Error", "Bugzilla could not log you in to get the information you requested since login name or password is incorrect.\nPlease check your settings in the bugzilla preferences. "); + BugzillaPlugin.log(e); + } + catch(IOException e){ + IStatus status = new MultiStatus( IBugzillaConstants.PLUGIN_ID, IStatus.ERROR, e.getClass().toString() + " occurred while opening the bug report. \n\nClick Details or see log for more information.", e); + IStatus s = new Status(IStatus.ERROR, IBugzillaConstants.PLUGIN_ID, IStatus.ERROR, e.getClass().toString() + ": ", e); + ((MultiStatus)status).add(s); + s = new Status (IStatus.ERROR, IBugzillaConstants.PLUGIN_ID, IStatus.ERROR, e.getMessage(), e); + ((MultiStatus)status).add(s); + + //write error to log + BugzillaPlugin.log(status); + + ErrorDialog.openError(null, "Bugzilla Error", null, status); + return false; + } + return true; + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchOperation.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchOperation.java new file mode 100644 index 000000000..4d39036a4 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchOperation.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.search; + +import javax.security.auth.login.LoginException; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.ui.actions.WorkspaceModifyOperation; + +/** + * An operation to perform Bugzilla search query. + */ +public class BugzillaSearchOperation extends WorkspaceModifyOperation implements IBugzillaSearchOperation +{ + /** The url of the bugzilla server */ + private String url; + + /** The bugzilla collector for the search */ + private IBugzillaSearchResultCollector collector; + + /** The bugzilla search query */ + private BugzillaSearchQuery query; + + /** The status of the search operation */ + private IStatus status; + + /** The LoginException that was thrown when trying to do the search */ + private LoginException loginException = null; + + /** + * Constructor + * @param url The url of the bugzilla server + * @param collector The bugzilla search collector to use for this search + */ + public BugzillaSearchOperation(String url, IBugzillaSearchResultCollector collector) + { + this.url = url; + this.collector = collector; + collector.setOperation(this); + } + + @Override + public void execute(IProgressMonitor monitor) { + // set the progress monitor for the search collector and start the search + collector.setProgressMonitor(monitor); + BugzillaSearchEngine engine = new BugzillaSearchEngine(url); + try + { + status = engine.search(collector); + } + catch(LoginException e) { + //save this exception to throw later + this.loginException = e; + } + } + + /** + * @see org.eclipse.mylar.bugzilla.search.IBugzillaSearchOperation#getImageDescriptor() + */ + public ImageDescriptor getImageDescriptor() { + return null; + } + + /** + * @see org.eclipse.mylar.bugzilla.search.IBugzillaSearchOperation#getStatus() + */ + public IStatus getStatus() throws LoginException { + // if a LoginException was thrown while trying to search, throw this + if (loginException == null) + return status; + else + throw loginException; + } + + /** + * @see org.eclipse.mylar.bugzilla.search.IBugzillaSearchOperation#getQuery() + */ + public BugzillaSearchQuery getQuery() { + return query; + } + + /** + * @see org.eclipse.mylar.bugzilla.search.IBugzillaSearchOperation#setQuery(org.eclipse.mylar.bugzilla.search.BugzillaSearchQuery) + */ + public void setQuery(BugzillaSearchQuery newQuery) { + this.query = newQuery; + } + +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchPage.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchPage.java new file mode 100644 index 000000000..8ce2ed248 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchPage.java @@ -0,0 +1,978 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.search; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.ArrayList; + +import javax.security.auth.login.LoginException; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.dialogs.DialogPage; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.dialogs.InputDialog; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.dialogs.ProgressMonitorDialog; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.mylar.bugzilla.BugzillaPlugin; +import org.eclipse.mylar.bugzilla.BugzillaPreferences; +import org.eclipse.mylar.bugzilla.IBugzillaConstants; +import org.eclipse.mylar.bugzilla.saveQuery.GetQueryDialog; +import org.eclipse.mylar.bugzilla.saveQuery.SaveQueryDialog; +import org.eclipse.mylar.bugzilla.saveQuery.SavedQueryFile; +import org.eclipse.search.ui.ISearchPage; +import org.eclipse.search.ui.ISearchPageContainer; +import org.eclipse.search.ui.NewSearchUI; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.List; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.internal.help.WorkbenchHelpSystem; + + +/** + * Bugzilla search page + */ +public class BugzillaSearchPage extends DialogPage implements ISearchPage { + private Combo summaryPattern = null; + private static ArrayList<BugzillaSearchData> previousSummaryPatterns = new ArrayList<BugzillaSearchData>(20); + private static ArrayList<BugzillaSearchData> previousEmailPatterns = new ArrayList<BugzillaSearchData>(20); + private static ArrayList<BugzillaSearchData> previousCommentPatterns = new ArrayList<BugzillaSearchData>(20); + private ISearchPageContainer scontainer = null; + private boolean firstTime = true; + + private IDialogSettings fDialogSettings; + + private static final String [] patternOperationText = {"all words", "any word", "regexp"}; + private static final String [] patternOperationValues = {"allwordssubstr", "anywordssubstr", "regexp"}; + private static final String [] emailOperationText = {"substring", "exact", "regexp"}; + private static final String [] emailOperationValues = {"substring", "exact", "regexp"}; + private static final String [] emailRoleValues = {"emailassigned_to1", "emailreporter1", "emailcc1", "emaillongdesc1"}; + + IPreferenceStore prefs = BugzillaPlugin.getDefault().getPreferenceStore(); + private String [] statusValues = BugzillaPreferences.queryOptionsToArray(prefs.getString(IBugzillaConstants.STATUS_VALUES)); + private String [] preselectedStatusValues = BugzillaPreferences.queryOptionsToArray(prefs.getString(IBugzillaConstants.PRESELECTED_STATUS_VALUES)); + private String [] resolutionValues = BugzillaPreferences.queryOptionsToArray(prefs.getString(IBugzillaConstants.RESOLUTION_VALUES)); + private String [] severityValues = BugzillaPreferences.queryOptionsToArray(prefs.getString(IBugzillaConstants.SEVERITY_VALUES)); + private String [] priorityValues = BugzillaPreferences.queryOptionsToArray(prefs.getString(IBugzillaConstants.PRIORITY_VALUES)); + private String [] hardwareValues = BugzillaPreferences.queryOptionsToArray(prefs.getString(IBugzillaConstants.HARDWARE_VALUES)); + private String [] osValues = BugzillaPreferences.queryOptionsToArray(prefs.getString(IBugzillaConstants.OS_VALUES)); + + private String [] productValues = BugzillaPreferences.queryOptionsToArray(prefs.getString(IBugzillaConstants.PRODUCT_VALUES)); + private String [] componentValues = BugzillaPreferences.queryOptionsToArray(prefs.getString(IBugzillaConstants.COMPONENT_VALUES)); + private String [] versionValues = BugzillaPreferences.queryOptionsToArray(prefs.getString(IBugzillaConstants.VERSION_VALUES)); + private String [] targetValues = BugzillaPreferences.queryOptionsToArray(prefs.getString(IBugzillaConstants.TARGET_VALUES)); + + private static class BugzillaSearchData { + /** Pattern to match on */ + String pattern; + /** Pattern matching criterion */ + int operation; + + BugzillaSearchData(String pattern, int operation) { + this.pattern = pattern; + this.operation = operation; + } + } + + /** + * The constructor. + */ + public BugzillaSearchPage() { + super(); + } + + /** + * Insert the method's description here. + * @see DialogPage#createControl + */ + public void createControl(Composite parent) { + readConfiguration(); + + Composite control = new Composite(parent, SWT.NONE); + GridLayout layout = new GridLayout(2, false); + control.setLayout(layout); + GridData gd = new GridData(GridData.FILL_BOTH); + control.setLayoutData(gd); + + createTextSearchComposite(control); + createComment(control); + createProductAttributes(control); + createLists(control); + createLastDays(control); + createEmail(control); + createSaveQuery(control); + input = new SavedQueryFile(BugzillaPlugin.getDefault().getStateLocation().toString(), "/queries"); + createUpdate(control); + + + setControl(control); + WorkbenchHelpSystem.getInstance().setHelp(control, IBugzillaConstants.SEARCH_PAGE_CONTEXT); + } + + private Control createTextSearchComposite(Composite control) { + GridData gd; + Label label; + + Composite group = new Composite(control, SWT.NONE); + GridLayout layout = new GridLayout(2, false); + group.setLayout(layout); + group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + gd = new GridData(GridData.BEGINNING | GridData.FILL_HORIZONTAL | GridData.GRAB_HORIZONTAL); + gd.horizontalSpan = 2; + group.setLayoutData(gd); + + // Info text + label = new Label(group, SWT.LEFT); + label.setText("Bug id or summary search terms"); + gd = new GridData(GridData.BEGINNING); + gd.horizontalSpan = 2; + label.setLayoutData(gd); + + // Pattern combo + summaryPattern = new Combo(group, SWT.SINGLE | SWT.BORDER); + summaryPattern.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + scontainer.setPerformActionEnabled(canQuery()); + } + }); + summaryPattern.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent e) { + handleWidgetSelected(summaryPattern, summaryOperation, previousSummaryPatterns); + } + }); + gd = new GridData(GridData.FILL_HORIZONTAL | GridData.GRAB_HORIZONTAL); + summaryPattern.setLayoutData(gd); + + summaryOperation = new Combo(group, SWT.SINGLE | SWT.READ_ONLY | SWT.BORDER); + summaryOperation.setItems(patternOperationText); + summaryOperation.setText(patternOperationText[0]); + summaryOperation.select(0); + + return group; + } + + + private Control createComment(Composite control) { + GridData gd; + Label label; + + Composite group = new Composite(control, SWT.NONE); + GridLayout layout = new GridLayout(3, false); + group.setLayout(layout); + group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + gd = new GridData(GridData.BEGINNING | GridData.FILL_HORIZONTAL | GridData.GRAB_HORIZONTAL); + gd.horizontalSpan = 2; + group.setLayoutData(gd); + + // Info text + label = new Label(group, SWT.LEFT); + label.setText("Comment contains: "); + gd = new GridData(GridData.BEGINNING); + label.setLayoutData(gd); + + commentOperation = new Combo(group, SWT.SINGLE | SWT.READ_ONLY | SWT.BORDER); + commentOperation.setItems(patternOperationText); + commentOperation.setText(patternOperationText[0]); + commentOperation.select(0); + + // Comment pattern combo + commentPattern = new Combo(group, SWT.SINGLE | SWT.BORDER); + commentPattern.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + scontainer.setPerformActionEnabled(canQuery()); + } + }); + commentPattern.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent e) { + handleWidgetSelected(commentPattern, commentOperation, previousCommentPatterns); + } + }); + gd = new GridData(GridData.FILL_HORIZONTAL | GridData.GRAB_HORIZONTAL); + commentPattern.setLayoutData(gd); + + return group; + } + + /** + * Creates the area for selection on product/component/version. + */ + private Control createProductAttributes(Composite control) { + GridData gd; + GridLayout layout; + + // Search expression + Group group = new Group(control, SWT.NONE); + layout = new GridLayout(); + layout.numColumns = 4; + group.setLayout(layout); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 5; + group.setLayoutData(gd); + + // Labels + Label label = new Label(group, SWT.LEFT); + label.setText("Product"); + + label = new Label(group, SWT.LEFT); + label.setText("Component"); + + label = new Label(group, SWT.LEFT); + label.setText("Version"); + + label = new Label(group, SWT.LEFT); + label.setText("Milestone"); + + // Lists + product = new List(group, SWT.MULTI | SWT.V_SCROLL | SWT.BORDER); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.heightHint = 40; + product.setLayoutData(gd); + + component = new List(group, SWT.MULTI | SWT.V_SCROLL | SWT.BORDER); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.heightHint = 40; + component.setLayoutData(gd); + + version = new List(group, SWT.MULTI | SWT.V_SCROLL | SWT.BORDER); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.heightHint = 40; + version.setLayoutData(gd); + + target = new List(group, SWT.MULTI | SWT.V_SCROLL | SWT.BORDER); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.heightHint = 40; + target.setLayoutData(gd); + + return group; + } + + /** + * Creates the area for selection of bug attributes (status, etc.) + */ + private Control createLists(Composite control) { + GridData gd; + GridLayout layout; + + // Search expression + Group group = new Group(control, SWT.NONE); + layout = new GridLayout(); + layout.numColumns = 6; + group.setLayout(layout); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.horizontalSpan = 5; + group.setLayoutData(gd); + + // Labels + Label label = new Label(group, SWT.LEFT); + label.setText("Status"); + + label = new Label(group, SWT.LEFT); + label.setText("Resolution"); + + label = new Label(group, SWT.LEFT); + label.setText("Severity"); + + label = new Label(group, SWT.LEFT); + label.setText("Priority"); + + label = new Label(group, SWT.LEFT); + label.setText("Hardware"); + + label = new Label(group, SWT.LEFT); + label.setText("OS"); + + // Lists + status = new List(group, SWT.MULTI | SWT.V_SCROLL | SWT.BORDER); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.heightHint = 40; + status.setLayoutData(gd); + + resolution = new List(group, SWT.MULTI | SWT.V_SCROLL | SWT.BORDER); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.heightHint = 40; + resolution.setLayoutData(gd); + + severity = new List(group, SWT.MULTI | SWT.V_SCROLL | SWT.BORDER); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.heightHint = 40; + severity.setLayoutData(gd); + + priority = new List(group, SWT.MULTI | SWT.V_SCROLL | SWT.BORDER); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.heightHint = 40; + priority.setLayoutData(gd); + + hardware = new List(group, SWT.MULTI | SWT.V_SCROLL | SWT.BORDER); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.heightHint = 40; + hardware.setLayoutData(gd); + + os = new List(group, SWT.MULTI | SWT.V_SCROLL | SWT.BORDER); + gd = new GridData(GridData.FILL_HORIZONTAL); + gd.heightHint = 40; + os.setLayoutData(gd); + + return group; + } + + private Text daysText; + + private Control createLastDays(Composite control) + { + GridLayout layout; + GridData gd; + + Group group = new Group(control, SWT.NONE); + layout = new GridLayout(3, false); + group.setLayout(layout); + group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + gd = new GridData(GridData.BEGINNING | GridData.FILL_HORIZONTAL | GridData.GRAB_HORIZONTAL); + gd.horizontalSpan = 2; + group.setLayoutData(gd); + + Label label = new Label(group, SWT.LEFT); + label.setText("Only bugs changed in the last "); + + // operation combo + daysText = new Text(group, SWT.BORDER); + daysText.setTextLimit(5); + daysText.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + String days = daysText.getText(); + if (days.length() == 0) + return; + for (int i = days.length() - 1; i >= 0; i--) { + try { + if (days.equals("") || Integer.parseInt(days) > -1) { + if (i == days.length() - 1) + return; + else + break; + } + } catch (NumberFormatException ex) { + days = days.substring(0, i); + } + } + daysText.setText(days); + } + }); + label = new Label(group, SWT.LEFT); + label.setText(" Days."); + + + return group; + } + + private static final String [] emailText = {"bug owner", "reporter", "CC list", "commenter"}; + private Control createEmail(Composite control) { + GridLayout layout; + GridData gd; + + Group group = new Group(control, SWT.NONE); + layout = new GridLayout(3, false); + group.setLayout(layout); + group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + gd = new GridData(GridData.BEGINNING | GridData.FILL_HORIZONTAL | GridData.GRAB_HORIZONTAL); + gd.horizontalSpan = 2; + group.setLayoutData(gd); + + Composite buttons = new Composite(group, SWT.NONE); + layout = new GridLayout(4, false); + buttons.setLayout(layout); + buttons.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + gd = new GridData(GridData.BEGINNING); + gd.horizontalSpan = 3; + buttons.setLayoutData(gd); + + emailButton = new Button[emailText.length]; + for (int i = 0; i < emailButton.length; i++) { + Button button = new Button(buttons, SWT.CHECK); + button.setText(emailText[i]); + emailButton[i] = button; + } + + Label label = new Label(group, SWT.LEFT); + label.setText("Email contains: "); + + // operation combo + emailOperation = new Combo(group, SWT.SINGLE | SWT.READ_ONLY | SWT.BORDER); + emailOperation.setItems(emailOperationText); + emailOperation.setText(emailOperationText[0]); + emailOperation.select(0); + + // pattern combo + emailPattern = new Combo(group, SWT.SINGLE | SWT.BORDER); + emailPattern.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + scontainer.setPerformActionEnabled(canQuery()); + } + }); + emailPattern.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent e) { + handleWidgetSelected(emailPattern, emailOperation, previousEmailPatterns); + } + }); + gd = new GridData(GridData.FILL_HORIZONTAL | GridData.GRAB_HORIZONTAL); + emailPattern.setLayoutData(gd); + + return group; + } + + /** + * Creates the buttons for remembering a query and accessing previously + * saved queries. + */ + private Control createSaveQuery(Composite control) { + GridLayout layout; + GridData gd; + + Group group = new Group(control, SWT.NONE); + layout = new GridLayout(3, false); + group.setLayout(layout); + group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + gd = new GridData(GridData.BEGINNING | GridData.FILL_HORIZONTAL | GridData.GRAB_HORIZONTAL); + gd.horizontalSpan = 2; + group.setLayoutData(gd); + + loadButton = new Button(group, SWT.PUSH | SWT.LEFT); + loadButton.setText("Saved Queries..."); + final BugzillaSearchPage bsp = this; + loadButton.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent event) { + GetQueryDialog qd = new GetQueryDialog(getShell(), + "Saved Queries", input); + if (qd.open() == InputDialog.OK) { + selIndex = qd.getSelected(); + if (selIndex != -1) { + rememberedQuery = true; + performAction(); + bsp.getShell().close(); + } + } + } + }); + loadButton.setEnabled(true); + loadButton.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING)); + + saveButton = new Button(group, SWT.PUSH | SWT.LEFT); + saveButton.setText("Remember..."); + saveButton.addSelectionListener(new SelectionAdapter() { + + @Override + public void widgetSelected(SelectionEvent event) { + SaveQueryDialog qd = new SaveQueryDialog(getShell(), + "Remember Query"); + if (qd.open() == InputDialog.OK) { + String qName = qd.getText(); + if (qName != null && qName.compareTo("") != 0) { + try { + input.add(getQueryParameters().toString(), qName, summaryPattern.getText()); + } + catch (UnsupportedEncodingException e) { + /* + * Do nothing. Every implementation of the Java platform is required + * to support the standard charset "UTF-8" + */ + } + } + } + } + }); + saveButton.setEnabled(true); + saveButton.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING)); + + return group; + } + + public static SavedQueryFile getInput() { + return input; + } + + private Control createUpdate(final Composite control) { + GridData gd; + Label label; + + Composite group = new Composite(control, SWT.NONE); + GridLayout layout = new GridLayout(2, false); + group.setLayout(layout); + group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + gd = new GridData(GridData.BEGINNING); + gd.horizontalSpan = 2; + group.setLayoutData(gd); + + // Info text + label = new Label(group, SWT.LEFT); + label.setText("Update search options from server (may take several seconds):"); + gd = new GridData(GridData.BEGINNING); + label.setLayoutData(gd); + + updateButton = new Button(group, SWT.LEFT | SWT.PUSH); + updateButton.setText("Update"); + + updateButton.setLayoutData(new GridData()); + + updateButton.addMouseListener(new MouseAdapter() { + + @Override + public void mouseUp(MouseEvent e) { + + monitorDialog.open(); + IProgressMonitor monitor = monitorDialog.getProgressMonitor(); + monitor.beginTask("Updating search options...", 55); + + try { + BugzillaPreferences.updateQueryOptions(monitor); + + product.setItems(BugzillaPreferences.queryOptionsToArray(prefs.getString(IBugzillaConstants.PRODUCT_VALUES))); + monitor.worked(1); + component.setItems(BugzillaPreferences.queryOptionsToArray(prefs.getString(IBugzillaConstants.COMPONENT_VALUES))); + monitor.worked(1); + version.setItems(BugzillaPreferences.queryOptionsToArray(prefs.getString(IBugzillaConstants.VERSION_VALUES))); + monitor.worked(1); + target.setItems(BugzillaPreferences.queryOptionsToArray(prefs.getString(IBugzillaConstants.TARGET_VALUES))); + monitor.worked(1); + status.setItems(BugzillaPreferences.queryOptionsToArray(prefs.getString(IBugzillaConstants.STATUS_VALUES))); + monitor.worked(1); + status.setSelection(BugzillaPreferences.queryOptionsToArray(prefs.getString(IBugzillaConstants.PRESELECTED_STATUS_VALUES))); + monitor.worked(1); + resolution.setItems(BugzillaPreferences.queryOptionsToArray(prefs.getString(IBugzillaConstants.RESOLUTION_VALUES))); + monitor.worked(1); + severity.setItems(BugzillaPreferences.queryOptionsToArray(prefs.getString(IBugzillaConstants.SEVERITY_VALUES))); + monitor.worked(1); + priority.setItems(BugzillaPreferences.queryOptionsToArray(prefs.getString(IBugzillaConstants.PRIORITY_VALUES))); + monitor.worked(1); + hardware.setItems(BugzillaPreferences.queryOptionsToArray(prefs.getString(IBugzillaConstants.HARDWARE_VALUES))); + monitor.worked(1); + os.setItems(BugzillaPreferences.queryOptionsToArray(prefs.getString(IBugzillaConstants.OS_VALUES))); + monitor.worked(1); + } + catch (LoginException exception) { + // we had a problem that seems to have been caused from bad login info + MessageDialog.openError(null, "Login Error", "Bugzilla could not log you in to get the information you requested since login name or password is incorrect.\nPlease check your settings in the bugzilla preferences. "); + BugzillaPlugin.log(exception); + } + finally { + monitor.done(); + monitorDialog.close(); + } + } + }); + + return group; + } + + private void handleWidgetSelected(Combo widget, Combo operation, ArrayList<BugzillaSearchData> history) { + if (widget.getSelectionIndex() < 0) + return; + int index = history.size() - 1 - widget.getSelectionIndex(); + BugzillaSearchData patternData= history.get(index); + if (patternData == null || !widget.getText().equals(patternData.pattern)) + return; + widget.setText(patternData.pattern); + operation.setText(operation.getItem(patternData.operation)); + } + + /** + * @see ISearchPage#performAction() + */ + public boolean performAction() { + getPatternData(summaryPattern, summaryOperation, previousSummaryPatterns); + getPatternData(commentPattern, commentOperation, previousCommentPatterns); + getPatternData(this.emailPattern, emailOperation, previousEmailPatterns); + + String summaryText; + String url; + if (rememberedQuery == true) { + url = getQueryURL(new StringBuffer(input.getQueryParameters(selIndex))); + summaryText = input.getSummaryText(selIndex); + } + else { + try { + StringBuffer params = getQueryParameters(); + url = getQueryURL(params); + summaryText = summaryPattern.getText(); + } + catch (UnsupportedEncodingException e) { + /* + * These statements should never be executed. Every implementation of + * the Java platform is required to support the standard charset + * "UTF-8" + */ + url = ""; + summaryText = ""; + } + } + + try { + // if the summary contains a single bug id, open the bug directly + int id = Integer.parseInt(summaryText); + return BugzillaSearchHit.show(id); + } catch (NumberFormatException ignored) { + // ignore this since this means that the text is not a bug id + } + + // Don't activate the search result view until it is known that the + // user is not opening a bug directly -- there is no need to open + // the view if no searching is going to take place. + NewSearchUI.activateSearchResultView(); + + BugzillaPlugin.getDefault().getPreferenceStore().setValue(IBugzillaConstants.MOST_RECENT_QUERY, summaryText); + + IBugzillaSearchResultCollector collector= new BugzillaSearchResultCollector(); + + IBugzillaSearchOperation op= new BugzillaSearchOperation( + url, collector); + + BugzillaSearchQuery searchQuery = new BugzillaSearchQuery(op); + NewSearchUI.runQueryInBackground(searchQuery); + + return true; + } + + /** + * @see ISearchPage#setContainer(ISearchPageContainer) + */ + public void setContainer(ISearchPageContainer container) { + scontainer = container; + } + + @Override + public void setVisible(boolean visible) { + if (visible && summaryPattern != null) { + if (firstTime) { + firstTime = false; + // Set item and text here to prevent page from resizing + summaryPattern.setItems(getPreviousPatterns(previousSummaryPatterns)); + commentPattern.setItems(getPreviousPatterns(previousCommentPatterns)); + emailPattern.setItems(getPreviousPatterns(previousEmailPatterns)); + + product.setItems(productValues); + component.setItems(componentValues); + version.setItems(versionValues); + target.setItems(targetValues); + + status.setItems(statusValues); + status.setSelection(preselectedStatusValues); + resolution.setItems(resolutionValues); + severity.setItems(severityValues); + priority.setItems(priorityValues); + hardware.setItems(hardwareValues); + os.setItems(osValues); + } + summaryPattern.setFocus(); + scontainer.setPerformActionEnabled(canQuery()); + } + super.setVisible(visible); + } + + /** + * Returns <code>true</code> if at least some parameter is given to query on. + */ + private boolean canQuery() { + return product.getSelectionCount() > 0 || + component.getSelectionCount() > 0 || + version.getSelectionCount() > 0 || + target.getSelectionCount() > 0 || + status.getSelectionCount() > 0 || + resolution.getSelectionCount() > 0 || + severity.getSelectionCount() > 0 || + priority.getSelectionCount() > 0 || + hardware.getSelectionCount() > 0 || + os.getSelectionCount() > 0 || + summaryPattern.getText().length() > 0 || + commentPattern.getText().length() > 0 || + emailPattern.getText().length() > 0; + } + + /** + * Return search pattern data and update search history list. + * An existing entry will be updated or a new one created. + */ + private BugzillaSearchData getPatternData(Combo widget, Combo operation, ArrayList<BugzillaSearchData> previousSearchQueryData) { + String pattern = widget.getText(); + if (pattern == null || pattern.trim().equals("")) { + return null; + } + BugzillaSearchData match = null; + int i = previousSearchQueryData.size() - 1; + while (i >= 0) { + match = previousSearchQueryData.get(i); + if (pattern.equals(match.pattern)) { + break; + } + i--; + } + if (i >= 0) { + match.operation = operation.getSelectionIndex(); + // remove - will be added last (see below) + previousSearchQueryData.remove(match); + } else { + match= new BugzillaSearchData(widget.getText(), operation.getSelectionIndex()); + } + previousSearchQueryData.add(match); + return match; + } + + /** + * Returns an array of previous summary patterns + */ + private String [] getPreviousPatterns(ArrayList<BugzillaSearchData> patternHistory) { + int size = patternHistory.size(); + String [] patterns = new String[size]; + for (int i = 0; i < size; i++) + patterns[i]= (patternHistory.get(size - 1 - i)).pattern; + return patterns; + } + + + private String getQueryURL(StringBuffer params) { + StringBuffer url = new StringBuffer(getQueryURLStart().toString()); + url.append(params); + return url.toString(); + } + + /** + * Creates the bugzilla query URL start. + * + * Example: https://bugs.eclipse.org/bugs/buglist.cgi? + */ + private StringBuffer getQueryURLStart() { + StringBuffer sb = new StringBuffer(BugzillaPlugin.getDefault().getServerName()); + + if (sb.charAt(sb.length()-1) != '/') { + sb.append('/'); + } + sb.append("buglist.cgi?"); + + // use the username and password if we have it + if(BugzillaPreferences.getUserName() != null && !BugzillaPreferences.getUserName().equals("") && BugzillaPreferences.getPassword() != null && !BugzillaPreferences.getPassword().equals("")) + { + try { + sb.append("GoAheadAndLogIn=1&Bugzilla_login=" + URLEncoder.encode(BugzillaPreferences.getUserName(), "UTF-8") + "&Bugzilla_password=" + URLEncoder.encode(BugzillaPreferences.getPassword(), "UTF-8") + "&"); + } catch (UnsupportedEncodingException e) { + /* + * Do nothing. Every implementation of the Java platform is required + * to support the standard charset "UTF-8" + */ + } + } + + return sb; + } + + /** + * Goes through the query form and builds up the query parameters. + * + * Example: short_desc_type=substring&short_desc=bla& ... + * @throws UnsupportedEncodingException + */ + private StringBuffer getQueryParameters() throws UnsupportedEncodingException { + StringBuffer sb = new StringBuffer(); + + sb.append("short_desc_type="); + sb.append(patternOperationValues[summaryOperation.getSelectionIndex()]); + + sb.append("&short_desc="); + sb.append(URLEncoder.encode(summaryPattern.getText(), "UTF-8")); + + int [] selected = product.getSelectionIndices(); + for (int i = 0; i < selected.length; i++) { + sb.append("&product="); + sb.append(URLEncoder.encode(product.getItem(selected[i]), "UTF-8")); + } + + selected = component.getSelectionIndices(); + for (int i = 0; i < selected.length; i++) { + sb.append("&component="); + sb.append(URLEncoder.encode(component.getItem(selected[i]), "UTF-8")); + } + + selected = version.getSelectionIndices(); + for (int i = 0; i < selected.length; i++) { + sb.append("&version="); + sb.append(URLEncoder.encode(version.getItem(selected[i]), "UTF-8")); + } + + selected = target.getSelectionIndices(); + for (int i = 0; i < selected.length; i++) { + sb.append("&target_milestone="); + sb.append(URLEncoder.encode(target.getItem(selected[i]), "UTF-8")); + } + + sb.append("&long_desc_type="); + sb.append(patternOperationValues[commentOperation.getSelectionIndex()]); + sb.append("&long_desc="); + sb.append(URLEncoder.encode(commentPattern.getText(), "UTF-8")); + + selected = status.getSelectionIndices(); + for (int i = 0; i < selected.length; i++) { + sb.append("&bug_status="); + sb.append(status.getItem(selected[i])); + } + + selected = resolution.getSelectionIndices(); + for (int i = 0; i < selected.length; i++) { + sb.append("&resolution="); + sb.append(resolution.getItem(selected[i])); + } + + selected = severity.getSelectionIndices(); + for (int i = 0; i < selected.length; i++) { + sb.append("&bug_severity="); + sb.append(severity.getItem(selected[i])); + } + + selected = priority.getSelectionIndices(); + for (int i = 0; i < selected.length; i++) { + sb.append("&priority="); + sb.append(priority.getItem(selected[i])); + } + + selected = hardware.getSelectionIndices(); + for (int i = 0; i < selected.length; i++) { + sb.append("&ref_platform="); + sb.append(URLEncoder.encode(hardware.getItem(selected[i]), "UTF-8")); + } + + selected = os.getSelectionIndices(); + for (int i = 0; i < selected.length; i++) { + sb.append("&op_sys="); + sb.append(URLEncoder.encode(os.getItem(selected[i]), "UTF-8")); + } + + if (emailPattern.getText() != null) { + for (int i = 0; i < emailButton.length; i++) { + if (emailButton[i].getSelection()) { + sb.append("&"); + sb.append(emailRoleValues[i]); + sb.append("=1"); + } + } + sb.append("&emailtype1="); + sb.append(emailOperationValues[emailOperation.getSelectionIndex()]); + sb.append("&email1="); + sb.append(URLEncoder.encode(emailPattern.getText(), "UTF-8")); + } + + if (daysText.getText() != null && !daysText.getText().equals("")) { + try + { + Integer.parseInt(daysText.getText()); + sb.append("&changedin="); + sb.append(URLEncoder.encode(daysText.getText(), "UTF-8")); + } + catch(NumberFormatException ignored) { + // this means that the days is not a number, so don't worry + } + } + + return sb; + } + + //--------------- Configuration handling -------------- + + // Dialog store id constants + private final static String PAGE_NAME = "BugzillaSearchPage"; //$NON-NLS-1$ + + private Combo summaryOperation; + + private List product; + + private List os; + + private List hardware; + + private List priority; + + private List severity; + + private List resolution; + + private List status; + + private Combo commentOperation; + + private Combo commentPattern; + + private List component; + + private List version; + + private List target; + + private Combo emailOperation; + + private Combo emailPattern; + + private Button [] emailButton; + + /** File containing saved queries */ + private static SavedQueryFile input; + + /** "Remember query" button */ + private Button saveButton; + /** "Saved queries..." button */ + private Button loadButton; + /** Run a remembered query */ + boolean rememberedQuery = false; + /** Index of the saved query to run */ + int selIndex; + + private Button updateButton; + + private ProgressMonitorDialog monitorDialog = new ProgressMonitorDialog(BugzillaPlugin.getDefault().getWorkbench().getActiveWorkbenchWindow().getShell()); + + /** + * Returns the page settings for this Java search page. + * + * @return the page settings to be used + */ + private IDialogSettings getDialogSettings() { + IDialogSettings settings = BugzillaPlugin.getDefault().getDialogSettings(); + fDialogSettings = settings.getSection(PAGE_NAME); + if (fDialogSettings == null) + fDialogSettings = settings.addNewSection(PAGE_NAME); + return fDialogSettings; + } + + /** + * Initializes itself from the stored page settings. + */ + private void readConfiguration() { + getDialogSettings(); + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchQuery.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchQuery.java new file mode 100644 index 000000000..3c3d7c268 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchQuery.java @@ -0,0 +1,115 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.search; + +import javax.security.auth.login.LoginException; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.mylar.bugzilla.BugzillaPlugin; +import org.eclipse.mylar.bugzilla.IBugzillaConstants; +import org.eclipse.search.ui.ISearchQuery; +import org.eclipse.search.ui.ISearchResult; +import org.eclipse.search.ui.text.AbstractTextSearchResult; +import org.eclipse.ui.PlatformUI; + + +/** + * This class performs a search query on Bugzilla bug reports. + * @see org.eclipse.search.ui.ISearchQuery + */ +public class BugzillaSearchQuery implements ISearchQuery { + + /** The collection of all the bugzilla matches. */ + private BugzillaSearchResult bugResult; + + /** The operation that performs the Bugzilla search query. */ + private IBugzillaSearchOperation operation; + + /** + * Constructor + * @param operation The operation that performs the Bugzilla search query. + */ + public BugzillaSearchQuery(IBugzillaSearchOperation operation) { + this.operation = operation; + operation.setQuery(this); + } + + /* (non-Javadoc) + * @see org.eclipse.search.ui.ISearchQuery#run(org.eclipse.core.runtime.IProgressMonitor) + */ + public IStatus run(IProgressMonitor monitor) throws OperationCanceledException { + final IStatus[] status = new IStatus[1]; + final AbstractTextSearchResult textResult= (AbstractTextSearchResult) getSearchResult(); + textResult.removeAll(); // Remove any existing search results from the view. + + try { + operation.execute(monitor); + status[0] = operation.getStatus(); + + if (status[0].getCode() == IStatus.CANCEL) { + status[0] = Status.OK_STATUS; + } + else if (!status[0].isOK()) { + PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable(){ + public void run(){ + ErrorDialog.openError(null, "Bugzilla Search Error", null, status[0]); + } + }); + status[0] = Status.OK_STATUS; + } + } + catch(LoginException e) { + // we had a problem while searching that seems like a login info problem + // thrown in BugzillaSearchOperation + MessageDialog.openError(null, "Login Error", "Bugzilla could not log you in to get the information you requested since login name or password is incorrect.\nPlease check your settings in the bugzilla preferences. "); + BugzillaPlugin.log(new Status( IStatus.ERROR, + IBugzillaConstants.PLUGIN_ID, IStatus.OK, "", e)); + } + return status[0]; + } + + /* (non-Javadoc) + * @see org.eclipse.search.ui.ISearchQuery#getLabel() + */ + public String getLabel() { + return BugzillaSearchEngine.QUERYING_SERVER; + } + + /* (non-Javadoc) + * @see org.eclipse.search.ui.ISearchQuery#canRerun() + */ + public boolean canRerun() { + return true; + } + + /* (non-Javadoc) + * @see org.eclipse.search.ui.ISearchQuery#canRunInBackground() + */ + public boolean canRunInBackground() { + return true; + } + + /* (non-Javadoc) + * @see org.eclipse.search.ui.ISearchQuery#getSearchResult() + */ + public ISearchResult getSearchResult() { + if (bugResult == null) { + bugResult= new BugzillaSearchResult(this); + } + return bugResult; + } + +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchResult.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchResult.java new file mode 100644 index 000000000..4dc00eee6 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchResult.java @@ -0,0 +1,132 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.search; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.mylar.bugzilla.ui.editor.ExistingBugEditorInput; +import org.eclipse.search.internal.ui.SearchPluginImages; +import org.eclipse.search.ui.ISearchQuery; +import org.eclipse.search.ui.text.AbstractTextSearchResult; +import org.eclipse.search.ui.text.IEditorMatchAdapter; +import org.eclipse.search.ui.text.IFileMatchAdapter; +import org.eclipse.search.ui.text.Match; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorPart; + + + +/** + * The collection of all the bugzilla matches. + * @see org.eclipse.search.ui.text.AbstractTextSearchResult + */ +public class BugzillaSearchResult extends AbstractTextSearchResult implements IEditorMatchAdapter { + + /** An empty array of matches */ + private final Match[] EMPTY_ARR= new Match[0]; + + /** + * The query producing this result. + */ + private BugzillaSearchQuery bugQuery; + + /** + * Constructor for <code>BugzillaSearchResult</code> class. + * + * @param query <code>BugzillaSearchQuery</code> that is producing this result. + */ + public BugzillaSearchResult(BugzillaSearchQuery query) { + super(); + bugQuery = query; + } + + @Override + public IEditorMatchAdapter getEditorMatchAdapter() { + return this; + } + + /** + * This function always returns <code>null</code>, as the matches for this implementation of <code>AbstractTextSearchResult</code> never contain files. + * @see org.eclipse.search.ui.text.AbstractTextSearchResult#getFileMatchAdapter() + */ + @Override + public IFileMatchAdapter getFileMatchAdapter() { + return null; + } + + /* (non-Javadoc) + * @see org.eclipse.search.ui.text.IEditorMatchAdapter#isShownInEditor(org.eclipse.search.ui.text.Match, org.eclipse.ui.IEditorPart) + */ + public boolean isShownInEditor(Match match, IEditorPart editor) { + IEditorInput ei= editor.getEditorInput(); + if (ei instanceof ExistingBugEditorInput) { + ExistingBugEditorInput bi= (ExistingBugEditorInput) ei; + return match.getElement().equals(bi.getBug()); + } + return false; + } + + /* (non-Javadoc) + * @see org.eclipse.search.ui.text.IEditorMatchAdapter#computeContainedMatches(org.eclipse.search.ui.text.AbstractTextSearchResult, org.eclipse.ui.IEditorPart) + */ + public Match[] computeContainedMatches(AbstractTextSearchResult result, IEditorPart editor) { + IEditorInput ei= editor.getEditorInput(); + if (ei instanceof ExistingBugEditorInput) { + ExistingBugEditorInput bi= (ExistingBugEditorInput) ei; + return getMatches(bi.getBug()); + } + return EMPTY_ARR; + } + + /* (non-Javadoc) + * @see org.eclipse.search.ui.ISearchResult#getLabel() + */ + public String getLabel() { + return getMatchCount() == 1 ? getSingularLabel() : getPluralLabel(); + } + + /** + * Get the singular label for the number of results + * @return The singular label + */ + protected String getSingularLabel() { + return "Bugzilla search - 1 match"; + } + + /** + * Get the plural label for the number of results + * @return The plural label + */ + protected String getPluralLabel() { + return "Bugzilla search - " + getMatchCount() + " matches"; + } + + /* (non-Javadoc) + * @see org.eclipse.search.ui.ISearchResult#getTooltip() + */ + public String getTooltip() { + return getLabel(); + } + + /* (non-Javadoc) + * @see org.eclipse.search.ui.ISearchResult#getImageDescriptor() + */ + public ImageDescriptor getImageDescriptor() { + return SearchPluginImages.DESC_OBJ_TSEARCH_DPDN; + } + + /* (non-Javadoc) + * @see org.eclipse.search.ui.ISearchResult#getQuery() + */ + public ISearchQuery getQuery() { + return bugQuery; + } + +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchResultCollector.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchResultCollector.java new file mode 100644 index 000000000..6e7c61fa9 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchResultCollector.java @@ -0,0 +1,225 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.search; + +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.mylar.bugzilla.IBugzillaConstants; +import org.eclipse.mylar.bugzilla.core.BugReport; +import org.eclipse.search.ui.NewSearchUI; +import org.eclipse.search.ui.text.Match; + + +/** + * Collects results of a Bugzilla search and inserts them into the search results view. + */ +public class BugzillaSearchResultCollector implements IBugzillaSearchResultCollector +{ + /** The bugzilla search operation */ + private IBugzillaSearchOperation operation; + + /** The collection of all the bugzilla matches */ + private BugzillaSearchResult searchResult; + + /** The progress monitor for the search operation */ + private IProgressMonitor monitor; + + /** The number of matches found */ + private int matchCount; + + /** The string to display to the user while querying */ + private static final String STARTING = "querying the server"; + + /** The string to display to the user when we have 1 match */ + private static final String MATCH = "1 match"; + + /** The string to display to the user when we have multiple or no matches */ + private static final String MATCHES = "{0} matches"; + + /** The string to display to the user when the query is done */ + private static final String DONE = "done"; + + /** Resource used to create markers */ + private static final IResource resource = ResourcesPlugin.getWorkspace().getRoot(); + + // TODO Find a better way to get the states and severity + + /** Array of severities for a bug */ + private static final String [] severity = {"blo", "cri", "maj", "nor", "min", "tri", "enh"}; + + /** Array of priorities for a bug */ + private static final String [] priority = {"P1", "P2", "P3", "P4", "P5", "--"}; + + /** Array of possible states of a bug */ + private static final String [] state = {"UNCO", "NEW", "ASSI", "REOP", "RESO", "VERI", "CLOS"}; + + /** Array of the possible resolutions of the bug */ + private static final String [] result = {"", "FIXE", "INVA", "WONT", "LATE", "REMI", "DUPL", "WORK"}; + + + /** + * @see org.eclipse.mylar.bugzilla.search.IBugzillaSearchResultCollector#aboutToStart(int) + */ + public void aboutToStart(int startMatchCount) throws CoreException + { + NewSearchUI.activateSearchResultView(); + matchCount = startMatchCount; + searchResult = (BugzillaSearchResult) getOperation().getQuery().getSearchResult(); + + // set the progress monitor to say that we are querying the server + monitor.setTaskName(STARTING); + } + + /** + * @see org.eclipse.mylar.bugzilla.search.IBugzillaSearchResultCollector#accept(org.eclipse.mylar.bugzilla.search.BugzillaSearchHit) + */ + public void accept(BugzillaSearchHit hit) throws CoreException + { + // set the markers to have the bugs attributes + IMarker marker = resource.createMarker(IBugzillaConstants.HIT_MARKER_ID); + marker.setAttribute(IBugzillaConstants.HIT_MARKER_ATTR_ID, new Integer(hit.getId())); + marker.setAttribute(IBugzillaConstants.HIT_MARKER_ATTR_DESC, hit.getDescription()); + marker.setAttribute(IBugzillaConstants.HIT_MARKER_ATTR_SEVERITY, mapValue(hit.getSeverity(), severity)); + marker.setAttribute(IBugzillaConstants.HIT_MARKER_ATTR_PRIORITY, mapValue(hit.getPriority(), priority)); + marker.setAttribute(IBugzillaConstants.HIT_MARKER_ATTR_PLATFORM, hit.getPlatform()); + marker.setAttribute(IBugzillaConstants.HIT_MARKER_ATTR_STATE, mapValue(hit.getState(), state)); + marker.setAttribute(IBugzillaConstants.HIT_MARKER_ATTR_RESULT, mapValue(hit.getResult(), result)); + marker.setAttribute(IBugzillaConstants.HIT_MARKER_ATTR_OWNER, hit.getOwner()); + marker.setAttribute(IBugzillaConstants.HIT_MARKER_ATTR_QUERY, hit.getQuery()); + + // Add the match to the search results view. + // The offset and length of the match are both 0, since the match is the + // bug report itself, not a subset of it. + searchResult.addMatch(new Match(marker, 0, 0)); + + // increment the match count + matchCount++; + + if (!getProgressMonitor().isCanceled()) + { + // if the operation is cancelled finish with whatever data was already found + getProgressMonitor().subTask(getFormattedMatchesString(matchCount)); + getProgressMonitor().worked(1); + } + } + + /** + * Returns a map where BugReport's attributes are entered into a Map using the same + * key/value pairs as those created on a search hit marker. + */ + public static Map<String, Object> getAttributeMap(BugReport bug) { + HashMap<String, Object> map = new HashMap<String, Object>(); + map.put(IBugzillaConstants.HIT_MARKER_ATTR_ID, new Integer(bug.getId())); + map.put(IBugzillaConstants.HIT_MARKER_ATTR_DESC, bug.getDescription()); + map.put(IBugzillaConstants.HIT_MARKER_ATTR_SEVERITY, mapValue(bug.getAttribute("Severity").getValue(), severity)); + map.put(IBugzillaConstants.HIT_MARKER_ATTR_PRIORITY, mapValue(bug.getAttribute("Priority").getValue(), priority)); + map.put(IBugzillaConstants.HIT_MARKER_ATTR_PLATFORM, bug.getAttribute("Hardware").getValue()); + map.put(IBugzillaConstants.HIT_MARKER_ATTR_STATE, mapValue(bug.getStatus(), state)); + map.put(IBugzillaConstants.HIT_MARKER_ATTR_RESULT, mapValue(bug.getResolution(), result)); + map.put(IBugzillaConstants.HIT_MARKER_ATTR_OWNER, bug.getAssignedTo()); + map.put(IBugzillaConstants.HIT_MARKER_ATTR_QUERY, ""); + return map; + } + + /** + * Get the map value for the given <code>String</code> value + * @param value The value that we are trying to map + * @param map The map that we are using + * @return The map value + */ + private static Integer mapValue(String value, String [] map) + { + // go through each element in the map + for (int i = 0; i < map.length; i++) + { + // if we found the value, return the position in the map + if (map[i].equals(value)) + { + return new Integer(i); + } + } + + // return null if we didn't find anything + return null; + } + + /** + * @see org.eclipse.mylar.bugzilla.search.IBugzillaSearchResultCollector#done() + */ + public void done() + { + if (!monitor.isCanceled()) + { + // if the operation is cancelled, finish with the data that we already have + String matchesString= getFormattedMatchesString(matchCount); + monitor.setTaskName(MessageFormat.format(DONE, new Object[]{matchesString})); + } + + // Cut no longer used references because the collector might be re-used + monitor = null; + searchResult = null; + } + + /** + * Get the string specifying the number of matches found + * @param count The number of matches found + * @return The <code>String</code> specifying the number of matches found + */ + private String getFormattedMatchesString(int count) + { + // if only 1 match, return the singular match string + if (count == 1) + return MATCH; + + // format the matches string and return it + Object[] messageFormatArgs = {new Integer(count)}; + return MessageFormat.format(MATCHES, messageFormatArgs); + } + + /** + * @see org.eclipse.mylar.bugzilla.search.IBugzillaSearchResultCollector#getProgressMonitor() + */ + public IProgressMonitor getProgressMonitor() + { + return monitor; + } + + /** + * @see org.eclipse.mylar.bugzilla.search.IBugzillaSearchResultCollector#setProgressMonitor(org.eclipse.core.runtime.IProgressMonitor) + */ + public void setProgressMonitor(IProgressMonitor monitor) + { + this.monitor = monitor; + } + + /** + * @see org.eclipse.mylar.bugzilla.search.IBugzillaSearchResultCollector#getOperation() + */ + public IBugzillaSearchOperation getOperation() + { + return operation; + } + + /** + * @see org.eclipse.mylar.bugzilla.search.IBugzillaSearchResultCollector#setOperation(org.eclipse.mylar.bugzilla.search.IBugzillaSearchOperation) + */ + public void setOperation(IBugzillaSearchOperation operation) + { + this.operation = operation; + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchResultView.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchResultView.java new file mode 100644 index 000000000..9d077dbb3 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchResultView.java @@ -0,0 +1,219 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.search; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.util.IPropertyChangeListener; +import org.eclipse.jface.util.PropertyChangeEvent; +import org.eclipse.jface.viewers.DecoratingLabelProvider; +import org.eclipse.jface.viewers.StructuredViewer; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.mylar.bugzilla.BugzillaPlugin; +import org.eclipse.mylar.bugzilla.IBugzillaConstants; +import org.eclipse.mylar.bugzilla.favorites.actions.AddFavoriteAction; +import org.eclipse.search.internal.ui.SearchMessages; +import org.eclipse.search.internal.ui.SearchPlugin; +import org.eclipse.search.internal.ui.SearchPreferencePage; +import org.eclipse.search.internal.ui.util.ExceptionHandler; +import org.eclipse.search.ui.IContextMenuConstants; +import org.eclipse.search.ui.text.AbstractTextSearchViewPage; +import org.eclipse.search.ui.text.Match; +import org.eclipse.ui.IPageLayout; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.part.IShowInTargetList; + + +/** + * Displays the results of a Bugzilla search. + * @see org.eclipse.search.ui.text.AbstractTextSearchViewPage + */ +public class BugzillaSearchResultView extends AbstractTextSearchViewPage implements IAdaptable { + + // The categories to sort bug results by + public static final int ORDER_ID = 1; + public static final int ORDER_SEVERITY = 2; + public static final int ORDER_PRIORITY = 3; + public static final int ORDER_STATUS = 4; + public static final int ORDER_DEFAULT = ORDER_ID; + + private static final String KEY_SORTING= IBugzillaConstants.PLUGIN_ID + ".search.resultpage.sorting"; //$NON-NLS-1$ + + private BugzillaContentProvider bugContentProvider; + private int bugCurrentSortOrder; + private BugzillaSortAction bugSortByIDAction; + private BugzillaSortAction bugSortBySeverityAction; + private BugzillaSortAction bugSortByPriorityAction; + private BugzillaSortAction bugSortByStatusAction; + private AddFavoriteAction addToFavoritesAction; + private OpenBugsAction openInEditorAction; + + private static final String[] SHOW_IN_TARGETS= new String[] { IPageLayout.ID_RES_NAV }; + private static final IShowInTargetList SHOW_IN_TARGET_LIST= new IShowInTargetList() { + public String[] getShowInTargetIds() { + return SHOW_IN_TARGETS; + } + }; + + private IPropertyChangeListener bugPropertyChangeListener; + + /** + * Constructor + */ + public BugzillaSearchResultView() { + // Only use the table layout. + super(FLAG_LAYOUT_FLAT); + + bugSortByIDAction = new BugzillaSortAction("Bug ID", this, ORDER_ID); + bugSortBySeverityAction = new BugzillaSortAction("Bug severity", this, ORDER_SEVERITY); + bugSortByPriorityAction = new BugzillaSortAction("Bug priority", this, ORDER_PRIORITY); + bugSortByStatusAction = new BugzillaSortAction("Bug status", this, ORDER_STATUS); + bugCurrentSortOrder = ORDER_DEFAULT; + + addToFavoritesAction = new AddFavoriteAction("Mark Result as Favorite", this); + openInEditorAction = new OpenBugsAction("Open Bug in Editor", this); + + bugPropertyChangeListener= new IPropertyChangeListener() { + public void propertyChange(PropertyChangeEvent event) { + if (SearchPreferencePage.LIMIT_TABLE.equals(event.getProperty()) || SearchPreferencePage.LIMIT_TABLE_TO.equals(event.getProperty())) + if (getViewer() instanceof TableViewer) { + getViewPart().updateLabel(); + getViewer().refresh(); + } + } + }; + SearchPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(bugPropertyChangeListener); + } + + @Override + protected void elementsChanged(Object[] objects) { + if (bugContentProvider != null) { + bugContentProvider.elementsChanged(objects); + } + } + + @Override + protected void clear() { + if (bugContentProvider != null) { + bugContentProvider.clear(); + } + } + + // Allows the inherited method "getViewer" to be accessed publicly. + @Override + public StructuredViewer getViewer() { + return super.getViewer(); + } + + @Override + protected void configureTreeViewer(TreeViewer viewer) { + // The tree layout is not used, so this function does not need to do anything. + } + + @Override + protected void configureTableViewer(TableViewer viewer) { + viewer.setUseHashlookup(true); + viewer.setLabelProvider(new DecoratingLabelProvider(new BugzillaLabelProvider(), PlatformUI.getWorkbench().getDecoratorManager().getLabelDecorator())); + viewer.setContentProvider(new BugzillaTableContentProvider(this)); + + // Set the order when the search view is loading so that the items are + // sorted right away + setSortOrder(bugCurrentSortOrder); + + bugContentProvider= (BugzillaContentProvider) viewer.getContentProvider(); + } + + /** + * Sets the new sorting category, and reorders all of the bug reports. + * @param sortOrder The new category to sort bug reports by + */ + public void setSortOrder(int sortOrder) { + bugCurrentSortOrder= sortOrder; + StructuredViewer viewer= getViewer(); + + switch (sortOrder) { + case ORDER_ID: + viewer.setSorter(new BugzillaIdSearchSorter()); + break; + case ORDER_PRIORITY: + viewer.setSorter(new BugzillaPrioritySearchSorter()); + break; + case ORDER_SEVERITY: + viewer.setSorter(new BugzillaSeveritySearchSorter()); + break; + case ORDER_STATUS: + viewer.setSorter(new BugzillaStateSearchSorter()); + break; + default: + // If the setting is not one of the four valid ones, + // use the default order setting. + sortOrder = ORDER_DEFAULT; + viewer.setSorter(new BugzillaIdSearchSorter()); + break; + } + + getSettings().put(KEY_SORTING, bugCurrentSortOrder); + } + + /* (non-Javadoc) + * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class) + */ + public Object getAdapter(Class adapter) { + if (IShowInTargetList.class.equals(adapter)) { + return SHOW_IN_TARGET_LIST; + } + return null; + } + + @Override + protected void showMatch(Match match, int currentOffset, int currentLength, boolean activate) throws PartInitException { + try { + Object element = getCurrentMatch().getElement(); + if (element instanceof IMarker) { + Integer id = (Integer) ((IMarker)element).getAttribute(IBugzillaConstants.HIT_MARKER_ATTR_ID); + BugzillaSearchHit.show(id.intValue()); + } + } + catch (CoreException e) { + // if an error occurs, handle and log it + ExceptionHandler.handle(e, SearchMessages.Search_Error_search_title, SearchMessages.Search_Error_search_message); //$NON-NLS-2$ //$NON-NLS-1$ + BugzillaPlugin.log(e.getStatus()); + } + } + + @Override + protected void fillContextMenu(IMenuManager mgr) { + super.fillContextMenu(mgr); + + // Create the submenu for sorting + MenuManager sortMenu= new MenuManager(SearchMessages.SortDropDownAction_label); //$NON-NLS-1$ + sortMenu.add(bugSortByIDAction); + sortMenu.add(bugSortByPriorityAction); + sortMenu.add(bugSortBySeverityAction); + sortMenu.add(bugSortByStatusAction); + + // Check the right sort option + bugSortByIDAction.setChecked(bugCurrentSortOrder == bugSortByIDAction.getSortOrder()); + bugSortByPriorityAction.setChecked(bugCurrentSortOrder == bugSortByPriorityAction.getSortOrder()); + bugSortBySeverityAction.setChecked(bugCurrentSortOrder == bugSortBySeverityAction.getSortOrder()); + bugSortByStatusAction.setChecked(bugCurrentSortOrder == bugSortByStatusAction.getSortOrder()); + + // Add the new context menu items + mgr.appendToGroup(IContextMenuConstants.GROUP_VIEWER_SETUP, sortMenu); + mgr.appendToGroup(IContextMenuConstants.GROUP_ADDITIONS, addToFavoritesAction); + mgr.appendToGroup(IContextMenuConstants.GROUP_OPEN, openInEditorAction); + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSeveritySearchSorter.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSeveritySearchSorter.java new file mode 100644 index 000000000..89765098c --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSeveritySearchSorter.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.search; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerSorter; +import org.eclipse.mylar.bugzilla.IBugzillaConstants; + + +/** + * Sorts results of Bugzilla search by bug severity. + */ +public class BugzillaSeveritySearchSorter extends ViewerSorter { + + /** + * Returns a negative, zero, or positive number depending on whether the + * first bug's severity goes before, is the same as, or goes after the + * second element's severity. + * <p> + * + * @see org.eclipse.jface.viewers.ViewerSorter#compare(org.eclipse.jface.viewers.Viewer, + * java.lang.Object, java.lang.Object) + */ + @Override + public int compare(Viewer viewer, Object e1, Object e2) + { + try + { + // cast the object and get its severity + IMarker entry1 = (IMarker) e1; + Integer severity1 = (Integer) entry1.getAttribute( + IBugzillaConstants.HIT_MARKER_ATTR_SEVERITY); + + // cast the other object and get its severity + IMarker entry2 = (IMarker) e2; + Integer severity2 = (Integer) entry2.getAttribute( + IBugzillaConstants.HIT_MARKER_ATTR_SEVERITY); + + // if neither is null, compare the bugs severities + if (severity1 != null && severity2 != null) + { + return severity1.compareTo(severity2); + } + } + catch (Exception ignored) { + // ignore if there is a problem + } + + // if that didn't work, use the default compare method + return super.compare(viewer, e1, e2); + } + + /** + * Returns the category of the given element. The category is a number used + * to allocate elements to bins; the bins are arranged in ascending numeric + * order. The elements within a bin are arranged via a second level sort + * criterion. + * <p> + * + * @see org.eclipse.jface.viewers.ViewerSorter#category(Object) + */ + @Override + public int category(Object element) + { + try + { + IMarker marker = (IMarker)element; + + // return the bugs id + if (marker.getType().equals(IBugzillaConstants.HIT_MARKER_ID)) + { + return ((Integer)marker.getAttribute(IBugzillaConstants.HIT_MARKER_ATTR_ID)).intValue(); + } + } + catch (Exception ignored) { + // ignore if there is a problem + } + + // if that didn't work, use the default category method + return super.category(element); + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSortAction.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSortAction.java new file mode 100644 index 000000000..84d94f47a --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSortAction.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.search; + +import org.eclipse.jface.action.Action; + +/** + * This class sorts Bugzilla search results by a supplied category. + */ +public class BugzillaSortAction extends Action { + + /** The category that this class sorts Bugzilla search results by. */ + private int bugSortOrder; + + /** The view where the Bugzilla search results are displayed. */ + private BugzillaSearchResultView bugPage; + + /** + * Constructor + * @param label The string used as the text for the action, or null if there is no text + * @param page The view where the Bugzilla search results are displayed. + * @param sortOrder The category that this class sorts Bugzilla search results by + */ + public BugzillaSortAction(String label, BugzillaSearchResultView page, int sortOrder) { + super(label); + bugPage= page; + bugSortOrder= sortOrder; + } + + /** + * Reorder the Bugzilla search results. + */ + @Override + public void run() { + bugPage.setSortOrder(bugSortOrder); + } + + /** + * Returns the category that this class sorts Bugzilla search results by. + */ + public int getSortOrder() { + return bugSortOrder; + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaStateSearchSorter.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaStateSearchSorter.java new file mode 100644 index 000000000..3c78eb8d5 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaStateSearchSorter.java @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.search; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerSorter; +import org.eclipse.mylar.bugzilla.IBugzillaConstants; + + +/** + * Sorts results of Bugzilla search by bug state. + */ +public class BugzillaStateSearchSorter extends ViewerSorter { + + /** + * Returns a negative, zero, or positive number depending on whether the + * first bug's state goes before, is the same as, or goes after the second + * element's state. + * <p> + * + * @see org.eclipse.jface.viewers.ViewerSorter#compare(org.eclipse.jface.viewers.Viewer, + * java.lang.Object, java.lang.Object) + */ + @Override + public int compare(Viewer viewer, Object e1, Object e2) + { + try + { + // cast the object and get its state + IMarker entry1 = (IMarker) e1; + Integer state1 = (Integer) entry1.getAttribute( + IBugzillaConstants.HIT_MARKER_ATTR_STATE); + + // cast the other object and get its state + IMarker entry2 = (IMarker) e2; + Integer state2 = (Integer) entry2.getAttribute( + IBugzillaConstants.HIT_MARKER_ATTR_STATE); + + // if neither is null, compare the bugs states + if (state1 != null && state2 != null) + { + // compare the states + int rc = state1.compareTo(state2); + + // compare the resolution if the states are the same + if (rc == 0) + { + // get the resolution of the bug + Integer result1 = (Integer) entry1.getAttribute( + IBugzillaConstants.HIT_MARKER_ATTR_RESULT); + + // get the resolution of the other bug + Integer result2 = (Integer) entry2.getAttribute( + IBugzillaConstants.HIT_MARKER_ATTR_RESULT); + + // if neither state is null, compare them + if (result1 != null && result2 != null) + { + rc = result1.compareTo(result2); + } + } + return rc; + } + + } + catch (Exception ignored) { + // ignore if there is a problem + } + + // if that didn't work, use the default compare method + return super.compare(viewer, e1, e2); + } + + /** + * Returns the category of the given element. The category is a number used + * to allocate elements to bins; the bins are arranged in ascending numeric + * order. The elements within a bin are arranged via a second level sort + * criterion. + * <p> + * + * @see org.eclipse.jface.viewers.ViewerSorter#category(Object) + */ + @Override + public int category(Object element) + { + try + { + IMarker marker = (IMarker)element; + + // return the bugs id + if (marker.getType().equals(IBugzillaConstants.HIT_MARKER_ID)) + { + return ((Integer)marker.getAttribute(IBugzillaConstants.HIT_MARKER_ATTR_ID)).intValue(); + } + } + catch (Exception ignored) { + // ignore if there is a problem + } + + // if that didn't work, use the default category method + return super.category(element); + } +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaTableContentProvider.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaTableContentProvider.java new file mode 100644 index 000000000..1e176d120 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaTableContentProvider.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.search; + +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.search.internal.ui.SearchPreferencePage; + +/** + * This implementation of <code>BugzillaContentProvider</code> is used for the + * table view of a Bugzilla search result. + */ +public class BugzillaTableContentProvider extends BugzillaContentProvider implements IStructuredContentProvider { + + /** The page the Bugzilla search results are displayed in */ + private BugzillaSearchResultView bugPage; + + /** + * Constructor + * @param page The page the Bugzilla search results are displayed in + */ + public BugzillaTableContentProvider(BugzillaSearchResultView page) { + bugPage = page; + } + + @Override + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + if (newInput instanceof BugzillaSearchResult) { + bugResult = (BugzillaSearchResult) newInput; + } + } + + @Override + public void elementsChanged(Object[] updatedElements) { + TableViewer viewer= getViewer(); + boolean tableLimited= SearchPreferencePage.isTableLimited(); + for (int i= 0; i < updatedElements.length; i++) { + if (bugResult.getMatchCount(updatedElements[i]) > 0) { + if (viewer.testFindItem(updatedElements[i]) != null) + viewer.update(updatedElements[i], null); + else { + if (!tableLimited || viewer.getTable().getItemCount() < SearchPreferencePage.getTableLimit()) + viewer.add(updatedElements[i]); + } + } else + viewer.remove(updatedElements[i]); + } + } + + /** + * Returns the viewer the bug results are displayed in. + */ + private TableViewer getViewer() { + return (TableViewer) bugPage.getViewer(); + } + + @Override + public void clear() { + getViewer().refresh(); + } + + /** + * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object) + */ + public Object[] getElements(Object inputElement) { + if (inputElement instanceof BugzillaSearchResult) { + Object[] elements= ((BugzillaSearchResult)inputElement).getElements(); + int tableLimit= SearchPreferencePage.getTableLimit(); + if (SearchPreferencePage.isTableLimited() && elements.length > tableLimit) { + Object[] shownElements= new Object[tableLimit]; + System.arraycopy(elements, 0, shownElements, 0, tableLimit); + return shownElements; + } + return elements; + } + return EMPTY_ARR; + } + +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/IBugzillaSearchOperation.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/IBugzillaSearchOperation.java new file mode 100644 index 000000000..774c76294 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/IBugzillaSearchOperation.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.search; + +import javax.security.auth.login.LoginException; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.resource.ImageDescriptor; + +/** + * Interface for the bugzilla search operation + * @author sminto + */ +public interface IBugzillaSearchOperation extends IRunnableWithProgress +{ + /** + * Execute the search + * @see org.eclipse.ui.actions.WorkspaceModifyOperation#execute(IProgressMonitor) + */ + public void execute(IProgressMonitor monitor); + + /** + * Get the status of the search operation + * @return The status of the search operation + * @throws LoginException + */ + public IStatus getStatus() throws LoginException; + + /** + * Get the image descriptor for the operation + * @return <code>null</code> + */ + public ImageDescriptor getImageDescriptor(); + + /** + * Get the bugzilla search query + * @return The bugzilla search query + */ + public BugzillaSearchQuery getQuery(); + + /** + * Sets the bugzilla search query + * @param newQuery The bugzilla search query to be set + */ + public void setQuery(BugzillaSearchQuery newQuery); +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/IBugzillaSearchResultCollector.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/IBugzillaSearchResultCollector.java new file mode 100644 index 000000000..24dfc3138 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/IBugzillaSearchResultCollector.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.search; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * Interface for the bugzilla search result collector. + * @author sminto + */ +public interface IBugzillaSearchResultCollector +{ + + /** + * Called before the actual search starts + * @param startCount - The starting count for the number of matches + * @throws CoreException + */ + public void aboutToStart(int startCount) throws CoreException; + + /** + * Accept a search hit and add it as a match and set the markers + * @param hit The search hit that was a match + * @throws CoreException + */ + public void accept(BugzillaSearchHit hit) throws CoreException; + + /** + * Called when the search has ended. + */ + public void done(); + + /** + * Get the progress monitor for the search + * @return The progress monitor + */ + public IProgressMonitor getProgressMonitor(); + + /** + * Set the progress monitor + * @param monitor The progress monitor the search should use + */ + public void setProgressMonitor(IProgressMonitor monitor); + + /** + * Set the current search operation + * @param operation The operation to set the search to + */ + public void setOperation(IBugzillaSearchOperation operation); + + /** + * Get the current operation + * @return The current search operation + */ + public IBugzillaSearchOperation getOperation(); +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/OpenBugsAction.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/OpenBugsAction.java new file mode 100644 index 000000000..609ad441d --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/OpenBugsAction.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.search; + +import java.util.Iterator; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.mylar.bugzilla.BugzillaPlugin; +import org.eclipse.mylar.bugzilla.IBugzillaConstants; +import org.eclipse.search.internal.ui.SearchMessages; +import org.eclipse.search.internal.ui.util.ExceptionHandler; + + +/** + * This class is used to open a bug report in an editor. + */ +public class OpenBugsAction extends Action { + + /** The view this action works on */ + private BugzillaSearchResultView resultView; + + /** + * Constructor + * @param text The text for this action + * @param resultView The <code>BugzillaSearchResultView</code> this action works on + */ + public OpenBugsAction(String text, BugzillaSearchResultView resultView) { + setText(text); + this.resultView = resultView; + } + + /** + * Open the selected bug reports in their own editors. + */ + @SuppressWarnings("unchecked") + @Override + public void run() { + + // Get the selected items + ISelection s = resultView.getViewer().getSelection(); + if (s instanceof IStructuredSelection) { + IStructuredSelection selection = (IStructuredSelection) s; + + // go through each of the selected items and show it in an editor + for (Iterator<IMarker> it = selection.iterator(); it.hasNext();) { + IMarker marker = it.next(); + try { + Integer id = (Integer) marker.getAttribute(IBugzillaConstants.HIT_MARKER_ATTR_ID); + BugzillaSearchHit.show(id.intValue()); + } + catch (CoreException e) { + // if an error occurs, handle and log it + ExceptionHandler.handle(e, SearchMessages.Search_Error_search_title, SearchMessages.Search_Error_search_message); //$NON-NLS-2$ //$NON-NLS-1$ + BugzillaPlugin.log(e.getStatus()); + } + } + + } + } + +} diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/BugzillaOpenStructure.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/BugzillaOpenStructure.java new file mode 100644 index 000000000..166a54aab --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/BugzillaOpenStructure.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.ui; + +/** + * Class to hold information about opening a bug report, such as what comment + * number to jump to + * @author sminto + */ +public class BugzillaOpenStructure{ + + private String server; + private int bugId; + private int commentNumber; + + /** + * Constructor + * @param server The server that the bug resides on + * @param bugId The id of the bug + * @param commentNumber The comment number to jump to when opened, or -1 + */ + public BugzillaOpenStructure(String server, int bugId, int commentNumber){ + this.bugId = bugId; + this.commentNumber = commentNumber; + this.server = server; + } + + /** + * Get the bug id to open + * @return The bug id + */ + public Integer getBugId() { + return bugId; + } + + /** + * Get the comment number to jump to + * @return The comment number or -1 if none + */ + public Integer getCommentNumber() { + return commentNumber; + } + + /** + * Get the server the bug resides on + * @return The server url string + */ + public String getServer(){ + return server; + } +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/FavoritesView.java b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/FavoritesView.java new file mode 100644 index 000000000..5e8379959 --- /dev/null +++ b/org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/FavoritesView.java @@ -0,0 +1,573 @@ +/******************************************************************************* + * Copyright (c) 2003 - 2005 University Of British Columbia and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * University Of British Columbia - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylar.bugzilla.ui; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.GroupMarker; +import org.eclipse.jface.action.IMenuListener; +import org.eclipse.jface.action.IMenuManager; +import org.eclipse.jface.action.IStatusLineManager; +import org.eclipse.jface.action.IToolBarManager; +import org.eclipse.jface.action.MenuManager; +import org.eclipse.jface.action.Separator; +import org.eclipse.jface.viewers.ColumnLayoutData; +import org.eclipse.jface.viewers.ColumnWeightData; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.ITableLabelProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.TableLayout; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.mylar.bugzilla.BugzillaPlugin; +import org.eclipse.mylar.bugzilla.IBugzillaConstants; +import org.eclipse.mylar.bugzilla.favorites.Favorite; +import org.eclipse.mylar.bugzilla.favorites.FavoritesFile; +import org.eclipse.mylar.bugzilla.favorites.actions.AbstractFavoritesAction; +import org.eclipse.mylar.bugzilla.favorites.actions.DeleteFavoriteAction; +import org.eclipse.mylar.bugzilla.favorites.actions.ViewFavoriteAction; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.TableItem; +import org.eclipse.ui.IActionBars; +import org.eclipse.ui.IMemento; +import org.eclipse.ui.IViewSite; +import org.eclipse.ui.IWorkbenchActionConstants; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.actions.ActionFactory; +import org.eclipse.ui.part.ViewPart; + + +/** + * A view that shows any bug marked as favorites. + */ +public class FavoritesView extends ViewPart { + + private static Composite savedParent; + + private IMemento savedMemento; + + private static DeleteFavoriteAction remove; + + public static DeleteFavoriteAction removeAll; + + public static SelectAllAction selectAll; + + private static ViewFavoriteAction open; + + private Table table; + + private MenuManager contextMenu; + + private static TableViewer viewer; + + private String[] columnHeaders = { + "Bug", + "Query", + "Date" + }; + + private ColumnLayoutData columnLayouts[] = { + new ColumnWeightData(10), + new ColumnWeightData(3), + new ColumnWeightData(5) + }; + + /** + * Constructor initializes favorites' source file initializes actions + */ + public FavoritesView() { + super(); + open = new ViewFavoriteAction(this); + selectAll = new SelectAllAction(); + remove = new DeleteFavoriteAction(this, false); + removeAll = new DeleteFavoriteAction(this, true); + } + + @Override + public void init(IViewSite site) throws PartInitException { + super.init(site); + } + + /** + * Initializes this view with the given view site. A memento is passed to + * the view which contains a snapshot of the views state from a previous + * session. + */ + @Override + public void init(IViewSite site, IMemento memento) throws PartInitException { + init(site); + this.savedMemento = memento; + } + + @Override + public void createPartControl(Composite parent) { + FavoritesView.savedParent = parent; + setPartName("Bugzilla Favorites"); + createTable(); + + viewer = new TableViewer(table); + viewer.setUseHashlookup(true); + createColumns(); + + GridData gd = new GridData(GridData.FILL_BOTH); + gd.verticalSpan = 20; + viewer.getTable().setLayoutData(gd); + + viewer.setContentProvider(new FavoritesViewContentProvider(this)); + viewer.setLabelProvider(new FavoritesViewLabelProvider()); + viewer.setInput(BugzillaPlugin.getDefault().getFavorites().elements()); + + viewer.addSelectionChangedListener(new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + FavoritesView.this.widgetSelected(event); + } + }); + + fillToolbar(); + createContextMenu(); + + Menu menu = contextMenu.createContextMenu(table); + table.setMenu(menu); + + hookGlobalActions(); + parent.layout(); + + // Restore state from the previous session. + restoreState(); + } + + @Override + public void setFocus() { + // don't need to do anything when the focus is set + } + + private void createColumns() { + TableLayout layout = new TableLayout(); + table.setLayout(layout); + table.setHeaderVisible(true); + + for (int i = 0; i < columnHeaders.length; i++) { + TableColumn tc = new TableColumn(table, SWT.NONE, i); + + tc.setText(columnHeaders[i]); + tc.pack(); + tc.setResizable(columnLayouts[i].resizable); + layout.addColumnData(columnLayouts[i]); + } + } + + private void createTable() { + + table = new Table(savedParent, SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.FULL_SELECTION); + table.setLinesVisible(true); + + // Add action support for a double-click + table.addMouseListener(new MouseAdapter() { + + @Override + public void mouseDoubleClick(MouseEvent e) { + open.run(); + } + }); + } + + private void fillToolbar() { + IActionBars actionBars = getViewSite().getActionBars(); + IToolBarManager toolbar = actionBars.getToolBarManager(); + + remove.setEnabled(false); + toolbar.add(remove); + toolbar.add(removeAll); + toolbar.add(new Separator()); + toolbar.add(selectAll); + + // create actions to handle the sorting of the favorites + sortByIDAction = new SortByAction(FavoritesFile.ID_SORT); + sortByIDAction.setText("by &Bug ID"); + sortByIDAction.setToolTipText("Sorts by Bug number"); + + sortByPriorityAction = new SortByAction(FavoritesFile.PRIORITY_SORT); + sortByPriorityAction.setText("by &Priority"); + sortByPriorityAction.setToolTipText("Sorts by priority of the bug"); + + sortBySeverityAction = new SortByAction(FavoritesFile.SEVERITY_SORT); + sortBySeverityAction.setText("by &Severity"); + sortBySeverityAction.setToolTipText("Sorts by severity of the bug"); + + sortByStatusAction = new SortByAction(FavoritesFile.STATE_SORT); + sortByStatusAction.setText("by S&tatus"); + sortByStatusAction.setToolTipText("Sorts by status of the bug"); + + // get the menu manager and create a submenu to contain sorting + IMenuManager menu = actionBars.getMenuManager(); + IMenuManager submenu = new MenuManager("&Sort"); + + // add the sorting actions to the menu bar + menu.add(submenu); + submenu.add(sortByIDAction); + submenu.add(sortBySeverityAction); + submenu.add(sortByPriorityAction); + submenu.add(sortByStatusAction); + + updateSortingState(); + } + + /** + * Function to make sure that the appropriate sort is checked + */ + void updateSortingState() { + int curCriterion = FavoritesFile.lastSel; + + sortByIDAction.setChecked(curCriterion == FavoritesFile.ID_SORT); + sortBySeverityAction.setChecked(curCriterion == FavoritesFile.SEVERITY_SORT); + sortByPriorityAction.setChecked(curCriterion == FavoritesFile.PRIORITY_SORT); + sortByStatusAction.setChecked(curCriterion == FavoritesFile.STATE_SORT); + viewer.setInput(viewer.getInput()); + } + + // Sorting actions for the favorites view + SortByAction sortByIDAction, sortBySeverityAction, sortByPriorityAction, sortByStatusAction; + + /** + * Inner class to handle sorting + * @author sminto + */ + class SortByAction extends Action { + /** The criteria to sort the favorites menu based on */ + private int criterion; + + /** + * Constructor + * @param criteria The criteria to sort the favorites menu based on + */ + public SortByAction(int criteria) { + this.criterion = criteria; + } + + /** + * Perform the sort + */ + @Override + public void run() { + BugzillaPlugin.getDefault().getFavorites().sort(criterion); + updateSortingState(); + } + } + + /** + * Create context menu. + */ + private void createContextMenu() { + contextMenu = new MenuManager("#FavoritesView"); + contextMenu.setRemoveAllWhenShown(true); + contextMenu.addMenuListener(new IMenuListener() { + public void menuAboutToShow(IMenuManager manager) { + fillContextMenu(manager); + updateActionEnablement(); + } + }); + + // Register menu for extension. + getSite().registerContextMenu("#FavoritesView", contextMenu, viewer); + } + + /** + * Hook global actions + */ + private void hookGlobalActions() { + IActionBars bars = getViewSite().getActionBars(); + bars.setGlobalActionHandler(ActionFactory.SELECT_ALL.getId(), selectAll); + bars.setGlobalActionHandler(ActionFactory.DELETE.getId(), remove); + table.addKeyListener(new KeyAdapter() { + + @Override + public void keyPressed(KeyEvent event) { + if (event.character == SWT.DEL && event.stateMask == 0 && + remove.isEnabled()) { + remove.run(); + } + } + }); + } + + /** + * Populate context menu + */ + private void fillContextMenu(IMenuManager mgr) { + mgr.add(open); + mgr.add(new Separator()); + mgr.add(new GroupMarker(IWorkbenchActionConstants.MB_ADDITIONS)); + mgr.add(new Separator()); + mgr.add(remove); + mgr.add(new DeleteFavoriteAction(this, true)); + mgr.add(new SelectAllAction()); + } + + /** + * Update action enablement depending on whether or not any items are selected. + * Displays name of current item in status bar. + */ + public static void updateActionEnablement() { + + boolean hasSelected = viewer.getTable().getSelectionCount() > 0; + remove.setEnabled(hasSelected); + open.setEnabled(hasSelected); + + boolean hasItems = viewer.getTable().getItemCount() > 0; + removeAll.setEnabled(hasItems); + selectAll.setEnabled(hasItems); + } + + @Override + public void saveState(IMemento memento) { + TableItem[] sel = table.getSelection(); + if (sel.length == 0) + return; + memento = memento.createChild("selection"); + for (int i = 0; i < sel.length; i++) { + memento.createChild("descriptor", new Integer(table.indexOf(sel[i])).toString()); + } + } + + private void restoreState() { + if (savedMemento == null) + return; + savedMemento = savedMemento.getChild("selection"); + if (savedMemento != null) { + IMemento descriptors[] = savedMemento.getChildren("descriptor"); + if (descriptors.length > 0) { + int[] objList = new int[descriptors.length]; + for (int nX = 0; nX < descriptors.length; nX++) { + String id = descriptors[nX].getID(); + objList[nX] = BugzillaPlugin.getDefault().getFavorites().find(Integer.valueOf(id).intValue()); + } + table.setSelection(objList); + } + } + viewer.setSelection(viewer.getSelection(), true); + savedMemento = null; + updateActionEnablement(); + } + + /** + * Returns list of names of selected items. + */ + public List<BugzillaOpenStructure> getBugIdsOfSelected() { + IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();//TableItem[] sel = table.getSelection(); + List<?> sel = selection.toList(); + List<BugzillaOpenStructure> Ids = new ArrayList<BugzillaOpenStructure>(); + + Iterator<?> itr = sel.iterator(); + while (itr.hasNext()) { + Object o = itr.next(); + if (o instanceof Favorite) { + Favorite entry = (Favorite) o; + Integer id = (Integer) entry.getAttributes().get(IBugzillaConstants.HIT_MARKER_ATTR_ID); + Ids.add(new BugzillaOpenStructure(entry.getServer(), id, -1)); + } + } + + return Ids; + } + + /** + * Calls remove function in FavoritesFile + */ + @SuppressWarnings("unchecked") + public void deleteSelectedFavorites() { + List<Favorite> selection = ((IStructuredSelection)viewer.getSelection()).toList(); + BugzillaPlugin.getDefault().getFavorites().remove(selection); + viewer.setInput(viewer.getInput()); + } + + /** + * Removes all of the favorites in the FavoritesFile. + */ + public void deleteAllFavorites() { + BugzillaPlugin.getDefault().getFavorites().removeAll(); + viewer.setInput(viewer.getInput()); + } + + /** + * Refreshes the view. + */ + public static void add() { + if (viewer != null) + viewer.setInput(viewer.getInput()); + } + + + /** + * @see SelectionListener#widgetSelected(SelectionEvent) + */ + @SuppressWarnings("unchecked") + public void widgetSelected(SelectionChangedEvent e) { + + IStructuredSelection selection = + (IStructuredSelection) e.getSelection(); + + boolean enable = selection.size() > 0; + selectAll.setEnabled(enable); + remove.setEnabled(enable); + open.setEnabled(enable); + + IStructuredSelection viewerSelection = (IStructuredSelection)viewer.getSelection();//TableItem[] sel = table.getSelection(); + List<Favorite> sel = viewerSelection.toList(); + if (sel.size() > 0) { + IStatusLineManager manager = this.getViewSite().getActionBars().getStatusLineManager(); + manager.setMessage(sel.get(0).toString());// table.getItem(selected).getText(0)); + } + + updateActionEnablement(); + } + + /** + * Attempts to display this view on the workbench. + */ + public static void checkWindow() { + if (savedParent == null || savedParent.isDisposed()) { + IWorkbenchWindow w = BugzillaPlugin.getDefault().getWorkbench() + .getActiveWorkbenchWindow(); + if (w != null) { |