blob: 68ff4565100d3391d48e29105293166265825df2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004 - 2006 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.mylyn.internal.bugzilla.core.internal;
import java.io.IOException;
import java.io.Reader;
import java.text.ParseException;
import javax.security.auth.login.LoginException;
import org.eclipse.mylyn.bugzilla.core.AbstractRepositoryReportAttribute;
import org.eclipse.mylyn.internal.bugzilla.core.IBugzillaConstants;
import org.eclipse.mylyn.internal.bugzilla.core.NewBugzillaReport;
import org.eclipse.mylyn.internal.bugzilla.core.internal.HtmlStreamTokenizer.Token;
/**
* @author Shawn Minto
*
* This class parses the valid attribute values for a new bug
*/
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(NewBugzillaReport 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 && attribute.contains(IBugzillaConstants.INVALID_2201_ATTRIBUTE_IGNORED)) {
continue;
}
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())) {
AbstractRepositoryReportAttribute a = new AbstractRepositoryReportAttribute(tag.getAttribute("name"));
a.setID(tag.getAttribute("name"));
a.setValue(tag.getAttribute("value"));
a.setHidden(true);
nbm.attributes.put(a.getName(), a);
continue;
}
}
}
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(NewBugzillaReport 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(NewBugzillaReport nbm, String attributeName) throws IOException, ParseException {
StringBuffer sb = new StringBuffer();
parseAttributeValueCell(nbm, attributeName, sb);
}
private void parseAttributeValueCell(NewBugzillaReport 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(NewBugzillaReport nbm, String attributeName, HtmlTag tag) throws IOException {
AbstractRepositoryReportAttribute a = new AbstractRepositoryReportAttribute(attributeName);
a.setID(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(NewBugzillaReport nbm, String attributeName, String parameterName) throws IOException,
ParseException {
boolean first = false;
AbstractRepositoryReportAttribute a = new AbstractRepositoryReportAttribute(attributeName);
a.setID(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;
}
}
}