Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormkersten2005-06-17 16:51:27 -0400
committermkersten2005-06-17 16:51:27 -0400
commit7320e6f5ba8114f927eeeed9f0b11ecd2041f414 (patch)
treee9a9d5d9e0ffb37dc8e575c8402a2008cea563d3
downloadorg.eclipse.mylyn.tasks-7320e6f5ba8114f927eeeed9f0b11ecd2041f414.tar.gz
org.eclipse.mylyn.tasks-7320e6f5ba8114f927eeeed9f0b11ecd2041f414.tar.xz
org.eclipse.mylyn.tasks-7320e6f5ba8114f927eeeed9f0b11ecd2041f414.zip
Initial mylar contribution
-rw-r--r--org.eclipse.mylyn-feature/.project17
-rw-r--r--org.eclipse.mylyn-feature/build.properties2
-rw-r--r--org.eclipse.mylyn-feature/epl-v10.html328
-rw-r--r--org.eclipse.mylyn-feature/feature.xml280
-rw-r--r--org.eclipse.mylyn-feature/license.html73
-rw-r--r--org.eclipse.mylyn.bugzilla-feature/.project17
-rw-r--r--org.eclipse.mylyn.bugzilla-feature/build.properties6
-rw-r--r--org.eclipse.mylyn.bugzilla-feature/epl-v10.html328
-rw-r--r--org.eclipse.mylyn.bugzilla-feature/feature.xml238
-rw-r--r--org.eclipse.mylyn.bugzilla-feature/license.html73
-rw-r--r--org.eclipse.mylyn.bugzilla.core/.classpath12
-rw-r--r--org.eclipse.mylyn.bugzilla.core/.cvsignore3
-rw-r--r--org.eclipse.mylyn.bugzilla.core/.project28
-rw-r--r--org.eclipse.mylyn.bugzilla.core/Icons/bug.gifbin0 -> 145 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/Icons/bugzilla-bookmark.gifbin0 -> 905 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/Icons/openresult.gifbin0 -> 157 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/Icons/remove-all.gifbin0 -> 117 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/Icons/remove.gifbin0 -> 97 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/Icons/selectAll.gifbin0 -> 149 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/META-INF/MANIFEST.MF36
-rw-r--r--org.eclipse.mylyn.bugzilla.core/about.html22
-rw-r--r--org.eclipse.mylyn.bugzilla.core/bugzilla_contexts.xml16
-rw-r--r--org.eclipse.mylyn.bugzilla.core/build.properties31
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/Bugzilla.gifbin0 -> 1662 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/book.html29
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start.html17
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/bugEditor.html56
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/bugWizard.html61
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/bugzillaFavorites.html47
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/bugzillaSearch.html66
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/bugzillaUpdate.html46
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/images/Icons/remove-all.gifbin0 -> 117 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/images/Icons/remove.gifbin0 -> 97 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-bug-editor.pngbin0 -> 13188 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-favorites-window.pngbin0 -> 7191 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-search-context.pngbin0 -> 25071 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-search-dialog.pngbin0 -> 23762 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-search-sorting.pngbin0 -> 14970 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-searchresults.pngbin0 -> 12779 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/images/existing-bug-editor.pngbin0 -> 24719 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/images/new-bug-wizard-attributes.pngbin0 -> 10595 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/images/new-bug-wizard-products.pngbin0 -> 11036 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/images/new-bug-wizard.pngbin0 -> 14750 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/images/offline-reports.pngbin0 -> 7126 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/images/prefs.pngbin0 -> 17837 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/images/previous-searches.pngbin0 -> 12742 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/images/remember-query-overwrite.pngbin0 -> 6687 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/images/remember-query.pngbin0 -> 4326 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/images/saved-query-list.pngbin0 -> 5074 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/images/update-search-dialog.pngbin0 -> 23503 bytes
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/offlineReports.html37
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/saveQuery.html50
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/setup.html52
-rw-r--r--org.eclipse.mylyn.bugzilla.core/docs/html/start/use.html19
-rw-r--r--org.eclipse.mylyn.bugzilla.core/plugin.xml131
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/BugzillaImages.java142
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/BugzillaPlugin.java288
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/BugzillaPreferences.java584
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/IBugzillaConstants.java93
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/TrustAll.java62
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/compare/BugzillaCompareInput.java120
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/compare/BugzillaCompareNode.java232
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/compare/BugzillaStructureCreator.java76
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/Attribute.java177
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/BugPost.java277
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/BugReport.java413
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/BugzillaException.java65
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/BugzillaRepository.java397
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/Comment.java165
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/IBugzillaBug.java95
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/Operation.java178
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/BugParser.java839
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/HtmlStreamTokenizer.java633
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/HtmlTag.java340
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/KeywordParser.java151
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/NewBugParser.java409
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/ProductConfiguration.java177
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/ProductConfigurationFactory.java194
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/core/internal/ProductParser.java148
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/Favorite.java110
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/FavoritesFile.java318
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/actions/AbstractFavoritesAction.java56
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/actions/AddFavoriteAction.java112
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/actions/AddToFavoritesAction.java49
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/actions/DeleteFavoriteAction.java69
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/favorites/actions/ViewFavoriteAction.java58
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/offlineReports/AbstractOfflineReportsAction.java56
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/offlineReports/DeleteOfflineReportAction.java69
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/offlineReports/OfflineReportsFile.java278
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/offlineReports/ViewOfflineReportAction.java86
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/saveQuery/GetQueryDialog.java203
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/saveQuery/SaveQueryDialog.java90
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/saveQuery/SavedQueryFile.java249
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaContentProvider.java58
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaIdSearchSorter.java85
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaLabelProvider.java94
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaPrioritySearchSorter.java85
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaQueryPageParser.java513
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchEngine.java293
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchHit.java341
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchOperation.java99
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchPage.java978
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchQuery.java115
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchResult.java132
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchResultCollector.java225
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSearchResultView.java219
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSeveritySearchSorter.java91
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaSortAction.java52
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaStateSearchSorter.java112
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/BugzillaTableContentProvider.java88
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/IBugzillaSearchOperation.java56
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/IBugzillaSearchResultCollector.java65
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/search/OpenBugsAction.java73
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/BugzillaOpenStructure.java59
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/FavoritesView.java573
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/OfflineView.java650
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/ViewBugzillaAction.java93
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/editor/AbstractBugEditor.java1522
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/editor/AbstractBugEditorInput.java67
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/editor/BugzillaEditorCopyAction.java39
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/editor/ExistingBugEditor.java742
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/editor/ExistingBugEditorInput.java85
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/editor/NewBugEditor.java361
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/editor/NewBugEditorInput.java48
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/outline/BugzillaOutlineComparer.java48
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/outline/BugzillaOutlineNode.java331
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/outline/BugzillaOutlinePage.java147
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/outline/BugzillaReportSelection.java162
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/outline/BugzillaTools.java62
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/outline/IBugzillaReportSelection.java88
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/wizard/AbstractBugWizard.java349
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/wizard/AbstractWizardDataPage.java620
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/wizard/AbstractWizardListPage.java211
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/wizard/NewBugModel.java215
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/wizard/NewBugWizard.java124
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/wizard/WizardAttributesPage.java30
-rw-r--r--org.eclipse.mylyn.bugzilla.core/src/org/eclipse/mylyn/bugzilla/ui/wizard/WizardProductPage.java114
-rw-r--r--org.eclipse.mylyn.bugzilla.core/toc.xml17
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/.classpath7
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/.cvsignore1
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/.project28
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/META-INF/MANIFEST.MF19
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/TestPages/bug-1-full.html719
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/TestPages/bug-not-found-eclipse.html158
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/TestPages/bug-not-found-hipikat.html142
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/TestPages/cdt-page.html184
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/TestPages/equinox-page.html177
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/TestPages/gmt-page.html175
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/TestPages/hipikat-copy-bug-1-full.html157
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/TestPages/platform-page.html299
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/TestPages/product-page-1-product-hipikat.html256
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/TestPages/product-page.html237
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/TestPages/ve-page.html178
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/about.html22
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/build.properties13
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/plugin.xml6
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/src/org/eclipse/mylyn/bugzilla/test/AllTests.java42
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/src/org/eclipse/mylyn/bugzilla/test/BugzillaNewBugParserTestCDT.java231
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/src/org/eclipse/mylyn/bugzilla/test/BugzillaNewBugParserTestEquinox.java216
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/src/org/eclipse/mylyn/bugzilla/test/BugzillaNewBugParserTestGMT.java217
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/src/org/eclipse/mylyn/bugzilla/test/BugzillaNewBugParserTestPlatform.java243
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/src/org/eclipse/mylyn/bugzilla/test/BugzillaNewBugParserTestVE.java223
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/src/org/eclipse/mylyn/bugzilla/test/BugzillaParserTest.java215
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/src/org/eclipse/mylyn/bugzilla/test/BugzillaParserTestNoBug.java46
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/src/org/eclipse/mylyn/bugzilla/test/BugzillaProductParser1ProductHipikatTest.java68
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/src/org/eclipse/mylyn/bugzilla/test/BugzillaProductParserTest.java80
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/src/org/eclipse/mylyn/bugzilla/test/BugzillaTestPlugin.java86
-rw-r--r--org.eclipse.mylyn.bugzilla.tests/src/org/eclipse/mylyn/bugzilla/test/FileTool.java256
-rw-r--r--org.eclipse.mylyn.help.ui/.classpath7
-rw-r--r--org.eclipse.mylyn.help.ui/.cvsignore1
-rw-r--r--org.eclipse.mylyn.help.ui/.project28
-rw-r--r--org.eclipse.mylyn.help.ui/META-INF/MANIFEST.MF14
-rw-r--r--org.eclipse.mylyn.help.ui/about.html22
-rw-r--r--org.eclipse.mylyn.help.ui/build.properties12
-rw-r--r--org.eclipse.mylyn.help.ui/plugin.xml15
-rw-r--r--org.eclipse.mylyn.help.ui/src/org/eclipse/mylyn/doc/MylarDocPlugin.java56
-rw-r--r--org.eclipse.mylyn.help.ui/toc.xml5
-rw-r--r--org.eclipse.mylyn.tasks.tests/.classpath11
-rw-r--r--org.eclipse.mylyn.tasks.tests/.cvsignore1
-rw-r--r--org.eclipse.mylyn.tasks.tests/.project28
-rw-r--r--org.eclipse.mylyn.tasks.tests/META-INF/MANIFEST.MF20
-rw-r--r--org.eclipse.mylyn.tasks.tests/about.html22
-rw-r--r--org.eclipse.mylyn.tasks.tests/build.properties5
-rw-r--r--org.eclipse.mylyn.tasks.tests/src/org/eclipse/mylyn/tasks/bugzilla/tests/BugzillaSearchPluginTest.java329
-rw-r--r--org.eclipse.mylyn.tasks.tests/src/org/eclipse/mylyn/tasks/bugzilla/tests/BugzillaStackTraceTest.java170
-rw-r--r--org.eclipse.mylyn.tasks.tests/src/org/eclipse/mylyn/tasks/bugzilla/tests/MylarBugzillaTestsPlugin.java39
-rw-r--r--org.eclipse.mylyn.tasks.tests/src/org/eclipse/mylyn/tasks/tests/Job.java87
-rw-r--r--org.eclipse.mylyn.tasks.tests/src/org/eclipse/mylyn/tasks/tests/MylarTasksTestsPlugin.java39
-rw-r--r--org.eclipse.mylyn.tasks.tests/src/org/eclipse/mylyn/tasks/tests/People.java67
-rw-r--r--org.eclipse.mylyn.tasks.tests/src/org/eclipse/mylyn/tasks/tests/Person.java106
-rw-r--r--org.eclipse.mylyn.tasks.tests/src/org/eclipse/mylyn/tasks/tests/TaskListManagerTest.java50
-rw-r--r--org.eclipse.mylyn.tasks.ui/.classpath11
-rw-r--r--org.eclipse.mylyn.tasks.ui/.cvsignore1
-rw-r--r--org.eclipse.mylyn.tasks.ui/.project30
-rw-r--r--org.eclipse.mylyn.tasks.ui/META-INF/MANIFEST.MF33
-rw-r--r--org.eclipse.mylyn.tasks.ui/about.html22
-rw-r--r--org.eclipse.mylyn.tasks.ui/build.properties24
-rw-r--r--org.eclipse.mylyn.tasks.ui/icons/elcl16/delete.gifbin0 -> 351 bytes
-rw-r--r--org.eclipse.mylyn.tasks.ui/icons/elcl16/refresh.gifbin0 -> 216 bytes
-rw-r--r--org.eclipse.mylyn.tasks.ui/icons/elcl16/remove.gifbin0 -> 163 bytes
-rw-r--r--org.eclipse.mylyn.tasks.ui/icons/elcl16/synched.gifbin0 -> 160 bytes
-rw-r--r--org.eclipse.mylyn.tasks.ui/icons/elcl16/task-bugzilla.gifbin0 -> 358 bytes
-rw-r--r--org.eclipse.mylyn.tasks.ui/icons/elcl16/task-category-new.gifbin0 -> 239 bytes
-rw-r--r--org.eclipse.mylyn.tasks.ui/icons/elcl16/task-category.gifbin0 -> 161 bytes
-rw-r--r--org.eclipse.mylyn.tasks.ui/icons/elcl16/task.gifbin0 -> 375 bytes
-rw-r--r--org.eclipse.mylyn.tasks.ui/icons/eview16/tasklist.gifbin0 -> 594 bytes
-rw-r--r--org.eclipse.mylyn.tasks.ui/plugin.xml47
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/BugzillaTask.java450
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/Category.java67
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/ITask.java97
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/ITaskActivityListener.java29
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/ITaskInfo.java23
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/MylarTasksPlugin.java224
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/Task.java306
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/TaskList.java138
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/TaskListManager.java159
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/bugzilla/BugzillaContentProvider.java59
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/bugzilla/BugzillaEditingMonitor.java46
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/bugzilla/BugzillaMylarBridge.java142
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/bugzilla/BugzillaReferencesProvider.java106
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/bugzilla/BugzillaReportNode.java184
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/bugzilla/BugzillaStructureBridge.java169
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/bugzilla/StackTrace.java375
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/bugzilla/Util.java226
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/bugzilla/search/BugzillaMylarSearch.java149
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/bugzilla/search/BugzillaMylarSearchJob.java109
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/bugzilla/search/BugzillaMylarSearchOperation.java526
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/bugzilla/search/BugzillaResultCollector.java182
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/bugzilla/ui/BugzillaNodeLabelProvider.java58
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/bugzilla/ui/BugzillaUiBridge.java141
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/ui/BugzillaTaskEditor.java274
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/ui/BugzillaTaskEditorInput.java135
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/ui/TaskEditor.java163
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/ui/TaskEditorCopyAction.java33
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/ui/TaskEditorInput.java113
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/ui/actions/ToggleIntersectionModeAction.java42
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/ui/views/ActiveTaskscapeView.java207
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/ui/views/TaskListLabelProvider.java213
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/ui/views/TaskListView.java1156
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/ui/views/ToolTipHandler.java240
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/util/RelativePathUtil.java31
-rw-r--r--org.eclipse.mylyn.tasks.ui/src/org/eclipse/mylyn/tasks/util/TaskTest.java148
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 (&quot;AGREEMENT&quot;). 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'>&quot;Contribution&quot; 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'>&quot;Contributor&quot; means any person or
+entity that distributes the Program.</span> </p>
+
+<p><span style='font-size:10.0pt'>&quot;Licensed Patents &quot; 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'>&quot;Program&quot; means the Contributions
+distributed in accordance with this Agreement.</span> </p>
+
+<p><span style='font-size:10.0pt'>&quot;Recipient&quot; 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 (&quot;Commercial
+Contributor&quot;) hereby agrees to defend and indemnify every other
+Contributor (&quot;Indemnified Contributor&quot;) against any losses, damages and
+costs (collectively &quot;Losses&quot;) 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 &quot;AS IS&quot; 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]>&nbsp;<![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 (&quot;AGREEMENT&quot;). ANY USE, REPRODUCTION OR
+DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT&apos;S ACCEPTANCE
+OF THIS AGREEMENT.
+1. DEFINITIONS
+&quot;Contribution&quot; 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
+&apos;originates&apos; from a Contributor if it was added to the Program
+by such Contributor itself or anyone acting on such Contributor&apos;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.
+&quot;Contributor&quot; means any person or entity that distributes the
+Program.
+&quot;Licensed Patents &quot; 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.
+&quot;Program&quot; means the Contributions distributed in accordance with
+this Agreement.
+&quot;Recipient&quot; 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&apos;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
+(&quot;Commercial Contributor&quot;) hereby agrees to defend and indemnify
+every other Contributor (&quot;Indemnified Contributor&quot;) against any
+losses, damages and costs (collectively &quot;Losses&quot;) 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&apos;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 &quot;AS IS&quot; 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&apos;s patent(s),
+then such Recipient&apos;s rights granted under Section 2(b) shall
+terminate as of the date such litigation is filed.
+All Recipient&apos;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&apos;s rights under this Agreement terminate, Recipient
+agrees to cease use and distribution of the Program as soon as
+reasonably practicable. However, Recipient&apos;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 &quot;CONTENT&quot;). 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
+ (&quot;EPL&quot;). 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, &quot;Program&quot; 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 (&quot;Repository&quot;) in CVS
+ modules (&quot;Modules&quot;) and made available as downloadable archives (&quot;Downloads&quot;).</p>
+
+<p>Content may be apportioned into plug-ins (&quot;Plug-ins&quot;), plug-in fragments (&quot;Fragments&quot;), and features (&quot;Features&quot;). A Feature is a bundle of one or more Plug-ins and/or Fragments and associated material. Files named &quot;feature.xml&quot; 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 &quot;plugins&quot; and Features are located in directories named &quot;features&quot;.</p>
+
+<p>Features may also include other Features (&quot;Included Features&quot;). Files named &quot;feature.xml&quot; 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 &quot;about.html&quot; (&quot;Abouts&quot;). The terms and conditions governing Features and
+Included Features should be contained in files named &quot;license.html&quot; (&quot;Feature Licenses&quot;). 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 &quot;src&quot; 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 (&quot;Feature Update License&quot;) 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 &quot;license&quot; property of files named &quot;feature.properties&quot;.
+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 (&quot;AGREEMENT&quot;). 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'>&quot;Contribution&quot; 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'>&quot;Contributor&quot; means any person or
+entity that distributes the Program.</span> </p>
+
+<p><span style='font-size:10.0pt'>&quot;Licensed Patents &quot; 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'>&quot;Program&quot; means the Contributions
+distributed in accordance with this Agreement.</span> </p>
+
+<p><span style='font-size:10.0pt'>&quot;Recipient&quot; 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 (&quot;Commercial
+Contributor&quot;) hereby agrees to defend and indemnify every other
+Contributor (&quot;Indemnified Contributor&quot;) against any losses, damages and
+costs (collectively &quot;Losses&quot;) 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 &quot;AS IS&quot; 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]>&nbsp;<![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 (&quot;AGREEMENT&quot;). ANY USE, REPRODUCTION OR
+DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT&apos;S ACCEPTANCE
+OF THIS AGREEMENT.
+1. DEFINITIONS
+&quot;Contribution&quot; 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
+&apos;originates&apos; from a Contributor if it was added to the Program
+by such Contributor itself or anyone acting on such Contributor&apos;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.
+&quot;Contributor&quot; means any person or entity that distributes the
+Program.
+&quot;Licensed Patents &quot; 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.
+&quot;Program&quot; means the Contributions distributed in accordance with
+this Agreement.
+&quot;Recipient&quot; 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&apos;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
+(&quot;Commercial Contributor&quot;) hereby agrees to defend and indemnify
+every other Contributor (&quot;Indemnified Contributor&quot;) against any
+losses, damages and costs (collectively &quot;Losses&quot;) 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&apos;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 &quot;AS IS&quot; 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&apos;s patent(s),
+then such Recipient&apos;s rights granted under Section 2(b) shall
+terminate as of the date such litigation is filed.
+All Recipient&apos;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&apos;s rights under this Agreement terminate, Recipient
+agrees to cease use and distribution of the Program as soon as
+reasonably practicable. However, Recipient&apos;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 &quot;CONTENT&quot;). 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
+ (&quot;EPL&quot;). 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, &quot;Program&quot; 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 (&quot;Repository&quot;) in CVS
+ modules (&quot;Modules&quot;) and made available as downloadable archives (&quot;Downloads&quot;).</p>
+
+<p>Content may be apportioned into plug-ins (&quot;Plug-ins&quot;), plug-in fragments (&quot;Fragments&quot;), and features (&quot;Features&quot;). A Feature is a bundle of one or more Plug-ins and/or Fragments and associated material. Files named &quot;feature.xml&quot; 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 &quot;plugins&quot; and Features are located in directories named &quot;features&quot;.</p>
+
+<p>Features may also include other Features (&quot;Included Features&quot;). Files named &quot;feature.xml&quot; 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 &quot;about.html&quot; (&quot;Abouts&quot;). The terms and conditions governing Features and
+Included Features should be contained in files named &quot;license.html&quot; (&quot;Feature Licenses&quot;). 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 &quot;src&quot; 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 (&quot;Feature Update License&quot;) 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 &quot;license&quot; property of files named &quot;feature.properties&quot;.
+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
new file mode 100644
index 000000000..7f4b2a8d8
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/Icons/bug.gif
Binary files differ
diff --git a/org.eclipse.mylyn.bugzilla.core/Icons/bugzilla-bookmark.gif b/org.eclipse.mylyn.bugzilla.core/Icons/bugzilla-bookmark.gif
new file mode 100644
index 000000000..89ca9d889
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/Icons/bugzilla-bookmark.gif
Binary files differ
diff --git a/org.eclipse.mylyn.bugzilla.core/Icons/openresult.gif b/org.eclipse.mylyn.bugzilla.core/Icons/openresult.gif
new file mode 100644
index 000000000..2b645a88f
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/Icons/openresult.gif
Binary files differ
diff --git a/org.eclipse.mylyn.bugzilla.core/Icons/remove-all.gif b/org.eclipse.mylyn.bugzilla.core/Icons/remove-all.gif
new file mode 100644
index 000000000..2c069ab3f
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/Icons/remove-all.gif
Binary files differ
diff --git a/org.eclipse.mylyn.bugzilla.core/Icons/remove.gif b/org.eclipse.mylyn.bugzilla.core/Icons/remove.gif
new file mode 100644
index 000000000..12a9167c5
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/Icons/remove.gif
Binary files differ
diff --git a/org.eclipse.mylyn.bugzilla.core/Icons/selectAll.gif b/org.eclipse.mylyn.bugzilla.core/Icons/selectAll.gif
new file mode 100644
index 000000000..e159862e3
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/Icons/selectAll.gif
Binary files differ
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
new file mode 100644
index 000000000..4d0c53d05
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/docs/html/Bugzilla.gif
Binary files differ
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
new file mode 100644
index 000000000..2c069ab3f
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/Icons/remove-all.gif
Binary files differ
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
new file mode 100644
index 000000000..12a9167c5
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/Icons/remove.gif
Binary files differ
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
new file mode 100644
index 000000000..192110478
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-bug-editor.png
Binary files differ
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
new file mode 100644
index 000000000..01f46ae83
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-favorites-window.png
Binary files differ
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
new file mode 100644
index 000000000..4c883b54d
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-search-context.png
Binary files differ
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
new file mode 100644
index 000000000..93098c8ea
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-search-dialog.png
Binary files differ
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
new file mode 100644
index 000000000..9ceb8f0e1
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-search-sorting.png
Binary files differ
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
new file mode 100644
index 000000000..5f538de0c
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/bugzilla-searchresults.png
Binary files differ
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
new file mode 100644
index 000000000..82ced89e4
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/existing-bug-editor.png
Binary files differ
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
new file mode 100644
index 000000000..027fea913
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/new-bug-wizard-attributes.png
Binary files differ
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
new file mode 100644
index 000000000..859533dba
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/new-bug-wizard-products.png
Binary files differ
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
new file mode 100644
index 000000000..d0e05339a
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/new-bug-wizard.png
Binary files differ
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
new file mode 100644
index 000000000..966384dcf
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/offline-reports.png
Binary files differ
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
new file mode 100644
index 000000000..c608502d1
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/prefs.png
Binary files differ
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
new file mode 100644
index 000000000..8bcb49d1b
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/previous-searches.png
Binary files differ
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
new file mode 100644
index 000000000..e16bbb8ca
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/remember-query-overwrite.png
Binary files differ
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
new file mode 100644
index 000000000..4d8ac7e1a
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/remember-query.png
Binary files differ
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
new file mode 100644
index 000000000..c7681ee9a
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/saved-query-list.png
Binary files differ
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
new file mode 100644
index 000000000..c28f642c6
--- /dev/null
+++ b/org.eclipse.mylyn.bugzilla.core/docs/html/start/images/update-search-dialog.png
Binary files differ
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("AssignedTo").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 (&lt;/TD&gt;) 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 (&lt;/TD&gt;) 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&amp;short_desc=bla&amp; ...
+ * @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) {
+ IWorkbenchPage page = w.getActivePage();
+ if (page != null) {
+ try {
+ page.showView(IBugzillaConstants.PLUGIN_ID + ".ui.favoritesView");
+ } catch (PartInitException pie) {
+ BugzillaPlugin.log(pie.getStatus());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Action class - "Select All"
+ */
+ public class SelectAllAction extends AbstractFavoritesAction {
+
+ public SelectAllAction() {
+ setToolTipText("Select all favorites");
+ setText("Select all");
+ setIcon("Icons/selectAll.gif");
+ }
+
+ @Override
+ public void run() {
+ checkWindow();
+ table.selectAll();
+ viewer.setSelection(viewer.getSelection(), true);
+ updateActionEnablement();
+ }
+ }
+
+ private class FavoritesViewLabelProvider extends LabelProvider implements ITableLabelProvider {
+
+ /**
+ * Returns the label text for the given column of a recommendation in the table.
+ */
+ public String getColumnText(Object element, int columnIndex) {
+ if (element instanceof Favorite) {
+ Favorite f = (Favorite) element;
+ switch (columnIndex) {
+ case 0:
+ return f.toString();
+ case 1:
+ return f.getQuery();
+ case 2:
+ return f.getDate().toString();
+ default:
+ return "Undefined column text";
+ }
+ }
+ return ""; //$NON-NLS-1$
+ }
+
+ /*
+ * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnImage(java.lang.Object, int)
+ */
+ public Image getColumnImage(Object arg0, int arg1) {
+ return null;
+ }
+ }
+
+ public void refresh() {
+ // don't need to do anything to refresh
+ }
+
+ private class FavoritesViewContentProvider implements IStructuredContentProvider {
+
+ private List results;
+
+ /**
+ * The constructor.
+ */
+ public FavoritesViewContentProvider(FavoritesView taskList) {
+ // no setup to do
+ }
+
+ /**
+ * Returns the elements to display in the viewer
+ * when its input is se