/*******************************************************************************
* Copyright 2005-2007, CHISEL Group, University of Victoria, Victoria, BC, Canada
* and IBM Corporation. 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:
* The Chisel Group, University of Victoria
*******************************************************************************/
package net.sourceforge.tagsea.parsed.comments;
import java.io.IOException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import net.sourceforge.tagsea.parsed.parser.BasicCommentParser;
import net.sourceforge.tagsea.parsed.parser.IParsedWaypointDescriptor;
import net.sourceforge.tagsea.parsed.parser.IReplacementProposal;
import net.sourceforge.tagsea.parsed.parser.IWaypointParseProblemCollector;
import net.sourceforge.tagsea.parsed.parser.ReplacementProposal;
import net.sourceforge.tagsea.parsed.parser.WaypointParseError;
import net.sourceforge.tagsea.parsed.parser.WaypointParseProblem;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.rules.EndOfLineRule;
import org.eclipse.jface.text.rules.IRule;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.RuleBasedScanner;
import org.eclipse.jface.text.rules.Token;
/**
* A parser that scans a document for comment regions and retrieves waypoint
* descriptors based on a standard syntax. The standard syntax is as follows:
* <code>&tag <i><tags></i> <i><attributes></i> <i>: message</i></code>
* Where <code>tags</code> is a list of white-space separated tags. Tag names
* may be delimited with '.' characters to generate a tag hierarchy.
* <code>attributes</code> is a list of attributes in the following format:
* <code>-name=value</code> If the value must contain white space or a colon
* (:), the value may be placed within double quotes ("). Valid names are
* <code>author</code> and <code>date</code>. Date values may specify a
* country and local. If a country and local are supplied, the following format
* is used: <code>-date="languageCOUNTRY:dateValue"</code> where
* <code>language</code> is the two-character ISO language code, and COUNTRY
* is the two-character ISO country code. If no locale is specified, then the
* default locale is used to try and parse the date. Date values are always
* specified in Java DateFormat.SHORT format. For example:
* <code>-date="enCA:01/01/01"</code> would represent January 1st, 2001 in
* Canadian English.
*
* @author Del Myers
*
*/
public class StandardCommentParser extends BasicCommentParser {
private String waypointKind;
private IDomainMethod domainMethod;
protected static final String TAG_STRING = "@tag"; //$NON-NLS-1$
protected static final Object TAG_KEY = new Object();
private static RuleBasedScanner fWaypointScanner;
static {
fWaypointScanner = new RuleBasedScanner();
EndOfLineRule waypointRule = new EndOfLineRule(TAG_STRING, new Token(
TAG_KEY));
fWaypointScanner.setRules(new IRule[] { waypointRule });
}
private class LocalWaypointParseProblem extends WaypointParseProblem {
private IReplacementProposal[] proposals;
private int severity;
private String message;
public LocalWaypointParseProblem(int start, int length,
IDocument document, String message, int severity,
IReplacementProposal proposal) {
this(start, length, document, message, severity, Arrays
.asList(new IReplacementProposal[] { proposal }));
}
public LocalWaypointParseProblem(int start, int length,
IDocument document, String message, int severity,
List<IReplacementProposal> proposals) {
super(start, length, document);
this.proposals = proposals
.toArray(new IReplacementProposal[proposals.size()]);
this.severity = severity;
this.message = message;
}
@Override
public String getMessage() {
return message;
}
@Override
public IReplacementProposal[] getProposals() {
return proposals;
}
@Override
public int getSeverity() {
return severity;
}
}
private class AttributeProposal implements IReplacementProposal {
private String displayString;
private String[] attrs;
private String[] values;
private int offset;
private int length;
public AttributeProposal(String displayString, String[] attrs,
String[] values, int offset, int length) {
this.displayString = displayString;
this.attrs = attrs;
this.values = values;
this.offset = offset;
this.length = length;
}
public void apply(IDocument document) throws BadLocationException {
String tagText = document.get(offset, length);
// scan for the colon
int location = 0;
boolean done = location >= tagText.length();
boolean inQuote = false;
String replaceString = "";
for (int i = 0; i < attrs.length; i++) {
replaceString = replaceString + attrs[i] + "=\"" + values[i]
+ "\" ";
}
while (!done) {
char c = tagText.charAt(location);
switch (c) {
case ':':
done = !inQuote;
break;
case '"':
inQuote = !inQuote;
break;
}
if (!done)
location++;
done = done || location >= tagText.length();
}
if (location < tagText.length()) {
if (tagText.charAt(location) == ':') {
// replace with the new text.
if (!Character.isWhitespace(tagText.charAt(location-1))) {
replaceString = " " + replaceString;
}
document.replace(offset + location, 0, replaceString);
}
} else if (location == tagText.length()) {
if (!Character.isWhitespace(tagText.charAt(location-1))) {
replaceString = " " + replaceString;
}
document.replace(offset+location, 0, replaceString);
}
}
public String getDisplayString() {
return displayString;
}
}
/**
* @param singleLine
* @param multiLineStart
* @param multiLineEnd
*/
public StandardCommentParser(String[] singleLine, String[] multiLineStart,
String[] multiLineEnd, String[] exclusionStart, String[] exclusionEnd,
String waypointKind) {
super(singleLine, multiLineStart, multiLineEnd, exclusionStart, exclusionEnd);
this.waypointKind = waypointKind;
}
@Override
protected List<IParsedWaypointDescriptor> doParseInComment(
IDocument document, IRegion region,
IWaypointParseProblemCollector collector) {
List<IRegion> waypointRegions = calculateWaypointRegions(document,
region);
LinkedList<IParsedWaypointDescriptor> descriptors = new LinkedList<IParsedWaypointDescriptor>();
String author = System.getProperty("user.name");
Locale locale = Locale.getDefault();
DateFormat format = DateFormat
.getDateInstance(DateFormat.SHORT, locale);
Date date = new Date();
String dateString = locale.getLanguage().toLowerCase()
+ locale.getCountry().toUpperCase() + ":" + format.format(date);
for (IRegion waypointRegion : waypointRegions) {
try {
int line = document.getLineOfOffset(waypointRegion.getOffset());
StandardCommentWaypointDescriptor d = StandardCommentTextParser
.parse(document.get(waypointRegion.getOffset(),
waypointRegion.getLength()), waypointRegion
.getOffset(), line);
if (d != null) {
d.setDetail(getDomainObject(document, region));
descriptors.add(d);
}
if (d.getAuthor() == null && d.getDate() == null) {
collector.accept(new LocalWaypointParseProblem(d
.getCharStart(), d.getCharEnd() - d.getCharStart(),
document, "Missing author and date",
WaypointParseProblem.SEVERITY_INFO,
new AttributeProposal("Add the author and date",
new String[] { "-author", "-date" },
new String[] { author, dateString }, d.getCharStart(),
d.getLength())));
}
if (d.getAuthor() == null) {
// add the author quick-fix
collector.accept(new LocalWaypointParseProblem(d
.getCharStart(), d.getCharEnd() - d.getCharStart(),
document, "Missing author",
WaypointParseProblem.SEVERITY_INFO,
new AttributeProposal("Add the author",
new String[] { "-author" },
new String[] { author }, d.getCharStart(),
d.getLength())));
} if (d.getDate() == null) {
// add the author quick-fix
collector.accept(new LocalWaypointParseProblem(d
.getCharStart(), d.getCharEnd() - d.getCharStart(),
document, "Missing date",
WaypointParseProblem.SEVERITY_INFO,
new AttributeProposal("Add the date",
new String[] { "-date" },
new String[] { dateString }, d.getCharStart(),
d.getLength())));
}
} catch (MalformedWaypointException e) {
int offset = e.getOffset() + waypointRegion.getOffset();
int length = e.getLength();
LinkedList<IReplacementProposal> proposals = new LinkedList<IReplacementProposal>();
;
try {
String text = document.get(offset, length);
ReplacementProposal authorProposal = new ReplacementProposal(
"Replace with author", offset, length, "-author=\""
+ author + "\"");
// add the date quick-fix
ReplacementProposal dateProposal = new ReplacementProposal(
"Replace with date", offset, length, "-date=\""
+ dateString + "\"");
switch (e.getType()) {
case ExpectedRValue:
case ExpectedEquals:
if (text.length() > 1 && "-author=".startsWith(text)) {
// add the author quick-fix
proposals.add(authorProposal);
} else if (text.length() > 1
&& "-date=".startsWith(text)) {
proposals.add(dateProposal);
} else {
proposals.add(authorProposal);
proposals.add(dateProposal);
}
}
} catch (BadLocationException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
if (proposals.size() != 0) {
collector.accept(new LocalWaypointParseProblem(offset,
length, document, e.getMessage(),
WaypointParseProblem.SEVERITY_ERROR, proposals));
} else {
collector.accept(new WaypointParseError(e.getMessage(), e
.getOffset()
+ waypointRegion.getOffset(), e.getLength(),
document));
}
} catch (IOException e) {
// TODO Auto-generated catch block and a really, really, really,
// really, really, really, really, long message.
e.printStackTrace();
} catch (BadLocationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return descriptors;
}
/**
* @param document
* @param region
* @return
*/
private List<IRegion> calculateWaypointRegions(IDocument document,
IRegion region) {
List<IRegion> waypointRegions = new ArrayList<IRegion>();
fWaypointScanner.setRange(document, region.getOffset(), region
.getLength());
while (true) {
IToken token = fWaypointScanner.nextToken();
if (token.getData() == TAG_KEY) {
Region r = new Region(fWaypointScanner.getTokenOffset(),
fWaypointScanner.getTokenLength());
waypointRegions.add(r);
} else if (token.equals(Token.EOF)) {
break;
}
}
return waypointRegions;
}
@Override
public final String getParsedWaypointKind() {
return waypointKind;
}
/**
* Sets the method for discovering what the domain-specific object is for
* this parser.
*
* @param method
*/
public void setDomainMethod(IDomainMethod method) {
this.domainMethod = method;
}
private String getDomainObject(IDocument document, IRegion region) {
if (domainMethod != null) {
return domainMethod.getDomainObject(document, region);
}
return null;
}
}