package edu.hawaii.ics.csdl.jupiter.file;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import javanet.staxutils.StaxUtilsXMLOutputFactory;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import edu.hawaii.ics.csdl.jupiter.ReviewException;
import edu.hawaii.ics.csdl.jupiter.file.review.CreationDate;
import edu.hawaii.ics.csdl.jupiter.file.review.LastModificationDate;
import edu.hawaii.ics.csdl.jupiter.file.review.Review;
import edu.hawaii.ics.csdl.jupiter.file.review.ReviewIssueMeta;
import edu.hawaii.ics.csdl.jupiter.model.review.ReviewId;
import edu.hawaii.ics.csdl.jupiter.model.review.ReviewModel;
import edu.hawaii.ics.csdl.jupiter.model.reviewissue.Resolution;
import edu.hawaii.ics.csdl.jupiter.model.reviewissue.ResolutionKeyManager;
import edu.hawaii.ics.csdl.jupiter.model.reviewissue.ReviewIssue;
import edu.hawaii.ics.csdl.jupiter.model.reviewissue.ReviewIssueModel;
import edu.hawaii.ics.csdl.jupiter.model.reviewissue.Severity;
import edu.hawaii.ics.csdl.jupiter.model.reviewissue.SeverityKeyManager;
import edu.hawaii.ics.csdl.jupiter.model.reviewissue.Status;
import edu.hawaii.ics.csdl.jupiter.model.reviewissue.StatusKeyManager;
import edu.hawaii.ics.csdl.jupiter.model.reviewissue.Type;
import edu.hawaii.ics.csdl.jupiter.model.reviewissue.TypeKeyManager;
import edu.hawaii.ics.csdl.jupiter.util.JupiterLogger;
import edu.hawaii.ics.csdl.jupiter.util.ResourceBundleKey;
/**
* Provides an utility methods to read and write XML file to/from CodeReviewContentProvider
* model instance. Please note that this class uses two classes called "ReviewIssue". The fully
* qualified ReviewIssue references refer to the the ReviewIssue class generated by JAXB. This
* is only used when reading and writing to XML. NOTE: even though JAXB has been removed, the
* generated classes are still in existence. This is merely an intermediate step and they will
* eventually be removed.
*
* @author Takuya Yamashita
* @version $Id: ReviewIssueXmlSerializer.java 155 2009-01-02 07:03:38Z jsakuda $
*/
public class ReviewIssueXmlSerializer {
/** Jupiter logger */
private static JupiterLogger log = JupiterLogger.getLogger();
/** The date format pattern string. */
private static final String DATE_FORMAT_PATTERN = "yyyy-MM-dd :: HH:mm:ss:SSS z";
/** The date formatter that uses the given date format string. */
private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat(
DATE_FORMAT_PATTERN);
/**
* Creates a Jupiter <code>ReviewIssue</code>.
*
* @param reviewIssue The Jupiter review issue to create an object of.
* @return Returns the <code>ReviewIssue</code> to write to file.
*/
private static edu.hawaii.ics.csdl.jupiter.file.review.ReviewIssue createXmlReviewIssue(
ReviewIssue reviewIssue) {
if (reviewIssue == null) {
throw new IllegalArgumentException("ReviewIssue instance is null.");
}
// create the JAXB review issue object
edu.hawaii.ics.csdl.jupiter.file.review.ReviewIssue xmlReviewIssue = new edu.hawaii.ics.csdl.jupiter.file.review.ReviewIssue();
// set id of the review
xmlReviewIssue.setId(reviewIssue.getIssueId());
// Creation date for review issue meta
CreationDate creationDate = new CreationDate();
creationDate.setFormat(DATE_FORMAT_PATTERN);
creationDate.setValue(DATE_FORMATTER.format(reviewIssue.getCreationDate()));
// Last modification date for review issue meta
LastModificationDate lastModDate = new LastModificationDate();
lastModDate.setFormat(DATE_FORMAT_PATTERN);
lastModDate.setValue(DATE_FORMATTER.format(reviewIssue.getModificationDate()));
// review issue meta
ReviewIssueMeta meta = new ReviewIssueMeta();
meta.setCreationDate(creationDate);
meta.setLastModificationDate(lastModDate);
xmlReviewIssue.setReviewIssueMeta(meta);
// reviewer id
xmlReviewIssue.setReviewerId(reviewIssue.getReviewer());
// assigned to
xmlReviewIssue.setAssignedTo(reviewIssue.getAssignedTo());
// review file
edu.hawaii.ics.csdl.jupiter.file.review.File reviewFile = new edu.hawaii.ics.csdl.jupiter.file.review.File();
String reviewLineString = reviewIssue.getLine();
if (!"".equals(reviewLineString)) {
// TODO Should Line be changed to an Integer in ReviewIssue?
reviewFile.setLine(Integer.parseInt(reviewLineString));
}
reviewFile.setValue(reviewIssue.getTargetFile());
xmlReviewIssue.setFile(reviewFile);
// type
xmlReviewIssue.setType(reviewIssue.getType().getKey());
// severity
xmlReviewIssue.setSeverity(reviewIssue.getSeverity().getKey());
// summary
xmlReviewIssue.setSummary(reviewIssue.getSummary());
// description
xmlReviewIssue.setDescription(reviewIssue.getDescription());
// annotation
xmlReviewIssue.setAnnotation(reviewIssue.getAnnotation());
// revision
xmlReviewIssue.setRevision(reviewIssue.getRevision());
// resolution
xmlReviewIssue.setResolution(reviewIssue.getResolution().getKey());
// status
xmlReviewIssue.setStatus(reviewIssue.getStatus().getKey());
return xmlReviewIssue;
}
/**
* Converts a JAXB ReviewIssue object into a Jupiter model <code>ReviewIssue</code>.
*
* @param xmlReviewIssue The JAXB review issue object.
* @param reviewIFile the review <code>iFile</code> instance to be stored in the
* <code>ReviewIssue</code> instance.
*
* @return The filled <code>ReviewIssue</code> instance.
*
* @throws ReviewException Thrown if a problem occurs when <code>ReviewIssue</code> is being
* created.
*/
private static ReviewIssue createReviewIssue(
edu.hawaii.ics.csdl.jupiter.file.review.ReviewIssue xmlReviewIssue, IFile reviewIFile)
throws ReviewException {
try {
ReviewIssueMeta reviewIssueMeta = xmlReviewIssue.getReviewIssueMeta();
CreationDate creationDate = reviewIssueMeta.getCreationDate();
Date createDate = createDate(creationDate.getValue(), creationDate.getFormat());
LastModificationDate lastModificationDate = reviewIssueMeta.getLastModificationDate();
Date lastModDate = createDate(lastModificationDate.getValue(), lastModificationDate
.getFormat());
String reviewerId = xmlReviewIssue.getReviewerId();
String assignedTo = xmlReviewIssue.getAssignedTo();
String summary = xmlReviewIssue.getSummary();
String description = xmlReviewIssue.getDescription();
String annotation = xmlReviewIssue.getAnnotation();
String revision = xmlReviewIssue.getRevision();
// String id = xmlReviewIssue.getId();
String lineString = "";
Integer line = xmlReviewIssue.getFile().getLine();
if (line != null) {
lineString = String.valueOf(line);
}
String targetFile = xmlReviewIssue.getFile().getValue();
String typeString = xmlReviewIssue.getType();
String severityString = xmlReviewIssue.getSeverity();
String resolutionString = xmlReviewIssue.getResolution();
String statusString = xmlReviewIssue.getStatus();
return new ReviewIssue(createDate, lastModDate, reviewerId, assignedTo, targetFile,
lineString, getType(typeString), getSeverity(severityString), summary, description,
annotation, revision, getResolution(resolutionString), getStatus(statusString),
reviewIFile);
}
catch (NullPointerException e) {
throw new ReviewException(e.getMessage());
}
}
/**
* Gets the <code>Type</code> instance from the type string.
*
* @param typeText The type key string. Note that this supports the 1.4.212 version or below
* to convert old text to a key.
*
* @return Returns the <code>Type</code> instance.
*/
private static Type getType(String typeText) {
String typeKey = "";
if (typeText.equals("Defect")) {
typeKey = ResourceBundleKey.ITEM_KEY_TYPE_DEFECT;
}
else if (typeText.equals("External_Issue")) {
typeKey = ResourceBundleKey.ITEM_KEY_TYPE_EXTERNAL_ISSUE;
}
else if (typeText.equals("Question")) {
typeKey = ResourceBundleKey.ITEM_KEY_TYPE_QUESTION;
}
else if (typeText.equals("Praise")) {
typeKey = ResourceBundleKey.ITEM_KEY_TYPE_PRAISE;
}
else {
typeKey = typeText;
}
ReviewModel reviewModel = ReviewModel.getInstance();
IProject project = reviewModel.getProjectManager().getProject();
ReviewId reviewId = reviewModel.getReviewIdManager().getReviewId();
return new Type(typeKey, TypeKeyManager.getInstance(project, reviewId).getOrdinal(typeKey));
}
/**
* Gets the <code>Severity</code> instance from the severity string.
*
* @param severityText The severity key string. Note that this supports the 1.4.212 version
* or below to convert old text to a key.
*
* @return Returns the <code>Severity</code> instance.
*/
private static Severity getSeverity(String severityText) {
String severityKey = "";
if (severityText.equals("Critical")) {
severityKey = ResourceBundleKey.ITEM_KEY_SEVERITY_CRITICAL;
}
else if (severityText.equals("Major")) {
severityKey = ResourceBundleKey.ITEM_KEY_SEVERITY_MAJOR;
}
else if (severityText.equals("Normal")) {
severityKey = ResourceBundleKey.ITEM_KEY_SEVERITY_NORMAL;
}
else if (severityText.equals("Minor")) {
severityKey = ResourceBundleKey.ITEM_KEY_SEVERITY_MINOR;
}
else if (severityText.equals("Trivial")) {
severityKey = ResourceBundleKey.ITEM_KEY_SEVERITY_TRIVIAL;
}
else {
severityKey = severityText;
}
ReviewModel reviewModel = ReviewModel.getInstance();
IProject project = reviewModel.getProjectManager().getProject();
ReviewId reviewId = reviewModel.getReviewIdManager().getReviewId();
SeverityKeyManager manager = SeverityKeyManager.getInstance(project, reviewId);
return new Severity(severityKey, manager.getOrdinal(severityKey));
}
/**
* Gets the <code>Resolution</code> instance from the resolution key string.
*
* @param resolutionText The resolution key string. Note that this supports the 1.4.212
* version or below to convert old text to a key.
*
* @return Returns the <code>Resolution</code> instance.
*/
private static Resolution getResolution(String resolutionText) {
String resolutionKey = "";
if (resolutionText.equals("Valid-Needsfixing")) {
resolutionKey = ResourceBundleKey.ITEM_KEY_RESOLUTION_VALID_NEEDSFIXING;
}
else if (resolutionText.equals("Valid-Wontfix")) {
resolutionKey = ResourceBundleKey.ITEM_KEY_RESOLUTION_VALID_WONTFIX;
}
else if (resolutionText.equals("Valid-Duplicate")) {
resolutionKey = ResourceBundleKey.ITEM_KEY_RESOLUTION_VALID_DUPLICATE;
}
else if (resolutionText.equals("Valid-Fixlater")) {
resolutionKey = ResourceBundleKey.ITEM_KEY_RESOLUTION_VALID_FIXLATER;
}
else if (resolutionText.equals("Invalid-Wontfix")) {
resolutionKey = ResourceBundleKey.ITEM_KEY_RESOLUTION_INVALID_WONTFIX;
}
else if (resolutionText.equals("Unsure-Validity")) {
resolutionKey = ResourceBundleKey.ITEM_KEY_RESOLUTION_UNSURE_VALIDITY;
}
else if (resolutionText.equals("Unset")) {
resolutionKey = ResourceBundleKey.ITEM_KEY_UNSET;
}
else {
resolutionKey = resolutionText;
}
ReviewModel reviewModel = ReviewModel.getInstance();
IProject project = reviewModel.getProjectManager().getProject();
ReviewId reviewId = reviewModel.getReviewIdManager().getReviewId();
ResolutionKeyManager manager = ResolutionKeyManager.getInstance(project, reviewId);
int resolutionOrdinal = manager.getOrdinal(resolutionKey);
return new Resolution(resolutionKey, resolutionOrdinal);
}
/**
* Gets the <code>Status</code> instance from the status key string.
*
* @param statusText The status key string. Note that this supports the 1.4.212 version or
* below to convert old text to a key.
*
* @return Returns the <code>Status</code> instance.
*/
private static Status getStatus(String statusText) {
String statusKey = "";
if (statusText.equals("Unresolved")) {
statusKey = ResourceBundleKey.ITEM_KEY_STATUS_UNRESOLVED;
}
else if (statusText.equals("Resolved")) {
statusKey = ResourceBundleKey.ITEM_KEY_STATUS_RESOLVED;
}
else {
statusKey = statusText;
}
ReviewModel reviewModel = ReviewModel.getInstance();
IProject project = reviewModel.getProjectManager().getProject();
ReviewId reviewId = reviewModel.getReviewIdManager().getReviewId();
StatusKeyManager manager = StatusKeyManager.getInstance(project, reviewId);
return new Status(statusKey, manager.getOrdinal(statusKey));
}
/**
* Creates the <code>Date</code> instance associated with the <code>dateString</code>. Note
* the this returns current time <code>Date</code> instance if <code>dateString</code> could
* not be parsed with <code>dateFormat</code>.
*
* @param dateString the date string to be parsed.
* @param dateFormat the date format to let parser know the date string to be parsed.
*
* @return the <code>Date</code> instance associated with the <code>dateString</code>.
*/
private static Date createDate(String dateString, String dateFormat) {
try {
return new SimpleDateFormat(dateFormat).parse(dateString);
}
catch (ParseException e) {
log.warning(e.getMessage());
return new Date();
}
}
/**
* Writes the <code>Review</code> to file.
*
* @param outputXml The file to write to.
* @param review The JAXB review object representing the data to write to file.
* @throws IOException Thrown if there are issues writing to file.
* @throws XMLStreamException Thrown if there are errors writing to the XML stream.
*/
private static void write(File outputXml, Review review) throws IOException, XMLStreamException {
StaxUtilsXMLOutputFactory xmlof = new StaxUtilsXMLOutputFactory(XMLOutputFactory.newInstance());
xmlof.setProperty(StaxUtilsXMLOutputFactory.INDENTING, true);
XMLStreamWriter writer = null;
try {
writer = xmlof.createXMLStreamWriter(new FileOutputStream(outputXml), "UTF-8");
writer.writeStartDocument("UTF-8", "1.0");
StaxReviewXmlUtil.writeReview(writer, review);
}
finally {
if (writer != null) {
try {
writer.close();
}
catch (XMLStreamException e) {
log.error(e);
}
}
}
}
/**
* Writes the content of all the code review data stored in the model into the XML file. Note
* that clients does not need to check if the specified file path (either directory or file)
* exists. The IFile parameter allows the file to be refreshed in Eclipse.
* <p>
* Clients should check if the passing <code>File</code> or/and <code>ReviewIssueModel</code>
* instance is not null. Otherwise the <code>IllegalArgumentException</code> is thrown.
*
* @param reviewId the review ID.
* @param model The model which contains the <code>ReviewIssue</code> instances.
* @param xmlFile The output XML file with absolute path.
* @param iFile The iFile instance for the review file.
*
* @throws IOException if problems occur.
* @throws XMLStreamException Thrown if there are errors writing the review to file.
*/
public static void write(ReviewId reviewId, ReviewIssueModel model, File xmlFile, IFile iFile)
throws IOException, XMLStreamException {
write(reviewId, model, xmlFile);
// try to refresh the file so Eclipse picks up the external changes
try {
iFile.refreshLocal(IResource.DEPTH_ZERO, null);
}
catch (CoreException e) {
log.debug("Cannot refresh review file " + xmlFile.getAbsolutePath());
}
}
/**
* Writes the content of all the code review data stored in the model into the XML file. Note
* that clients does not need to check if the specified file path (either directory or file)
* exists.
* <p>
* Clients should check if the passing <code>File</code> or/and <code>ReviewIssueModel</code>
* instance is not null. Otherwise the <code>IllegalArgumentException</code> is thrown.
*
* @param reviewId the review ID.
* @param model The model which contains the <code>ReviewIssue</code> instances.
* @param xmlFile The output XML file with absolute path.
*
* @throws IOException if problems occur.
* @throws XMLStreamException Thrown if there are errors writing the review to file.
*/
public static void write(ReviewId reviewId, ReviewIssueModel model, File xmlFile)
throws IOException, XMLStreamException {
if (xmlFile == null) {
log.debug("XML file instance is null.");
throw new IllegalArgumentException("File instance is null.");
}
if (model == null) {
log.debug("Model is null.");
throw new IllegalArgumentException("ReviewIssueModel instance is null.");
}
// if outputFile does not exit, create them.
if (!xmlFile.getParentFile().exists()) {
try {
xmlFile.getParentFile().mkdirs();
}
catch (SecurityException e) {
// show can-not-create directory message.
log.debug(e.getMessage());
throw new SecurityException(e.getMessage());
}
}
if (!xmlFile.exists()) {
try {
xmlFile.createNewFile();
}
catch (SecurityException e) {
log.debug(e.getMessage());
throw new SecurityException(e.getMessage());
}
}
if (!xmlFile.canWrite()) {
// show can-not-write message in a file.
log.debug(xmlFile + " can not be written");
throw new RuntimeException(xmlFile + " is not writable to record review issue. "
+ "Please make it writable before proceed. Issue from " + reviewId.getAuthor()
+ reviewId.getAuthor() + " with message \"" + reviewId.getDescription()
+ "\" is not saved.");
}
Review review = new Review();
review.setId(reviewId.getReviewId());
if (model != null) {
for (Iterator<ReviewIssue> i = model.iterator(); i.hasNext();) {
ReviewIssue reviewIssue = i.next();
if (xmlFile.equals(reviewIssue.getReviewIFile().getLocation().toFile())) {
edu.hawaii.ics.csdl.jupiter.file.review.ReviewIssue xmlReviewIssue = createXmlReviewIssue(reviewIssue);
review.getReviewIssue().add(xmlReviewIssue);
}
}
}
log.debug("writing " + xmlFile + " ...");
write(xmlFile, review);
}
/**
* Writes empty code review XML file.
*
* @param xmlFile the <code>File</code> XML file.
* @throws ReviewException if problems occur during writing process.
*/
public static void writeEmptyCodeReview(File xmlFile) throws ReviewException {
if (xmlFile == null) {
log.debug("XML file instance is null.");
throw new IllegalArgumentException("File instance is null.");
}
Review review = new Review();
ReviewModel reviewModel = ReviewModel.getInstance();
ReviewId reviewId = reviewModel.getReviewIdManager().getReviewId();
review.setId(reviewId.getReviewId());
try {
write(xmlFile, review);
}
catch (IOException e) {
throw new ReviewException("IOException: " + e.getMessage());
}
catch (XMLStreamException e) {
throw new ReviewException("JAXBException: " + e.getMessage());
}
}
/**
* Reads the xmlFile of the <code>IFile</code> instance to create <code>ReviewIssue</code>
* instances in the <code>ReviewIssueModel</code> instance. Note that the review file will be
* skipped if the problems occur on the reading process (e.g. XML file is not valid due to
* error in XML structure).
*
* @param reviewId the review id.
* @param model The <code>ReviewIssueModel</code> instance to hold <code>ReviewIssue</code>
* instances.
* @param iFiles The array of IFile implementing class instance to hold file path.
* @return <code>true</code> if all files are successfully read. <code>false</code> if there
* is a file to be unread.
*/
public static boolean read(ReviewId reviewId, ReviewIssueModel model, IFile[] iFiles) {
if (reviewId == null || model == null || iFiles == null) {
throw new IllegalArgumentException("ReviewId, ReviewIssueModel, or IFile[] is null");
}
XMLInputFactory xmlif = XMLInputFactory.newInstance();
xmlif.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, Boolean.TRUE);
xmlif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
xmlif.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
boolean isSuccessIterationForAll = true;
for (int i = 0; i < iFiles.length; i++) {
log.debug("reading " + iFiles[i].getLocation() + " ...");
File xmlFile = new File(iFiles[i].getLocation().toString());
Review review;
try {
XMLStreamReader reader = xmlif.createXMLStreamReader(xmlFile.getAbsolutePath(),
new FileInputStream(xmlFile));
review = StaxReviewXmlUtil.parseReviewFile(reader);
}
catch (Exception e) {
log.error(e);
isSuccessIterationForAll = false;
continue;
}
String reviewIdName = review.getId();
if (reviewIdName != null && reviewId != null
&& reviewIdName.equals(reviewId.getReviewId())) {
boolean isSuccessIterationForFile = true;
List<edu.hawaii.ics.csdl.jupiter.file.review.ReviewIssue> xmlReviewIssues = review
.getReviewIssue();
List<ReviewIssue> tempCodeReviewList = new ArrayList<ReviewIssue>();
for (edu.hawaii.ics.csdl.jupiter.file.review.ReviewIssue xmlReviewIssue : xmlReviewIssues) {
try {
ReviewIssue reviewIssue = createReviewIssue(xmlReviewIssue, iFiles[i]);
tempCodeReviewList.add(reviewIssue);
}
catch (ReviewException e) {
log.error(e);
isSuccessIterationForFile = false;
isSuccessIterationForAll = false;
break;
}
}
// adds the list to the model only when the iteration of the file succeed.
if (isSuccessIterationForFile) {
model.addAll(tempCodeReviewList);
}
}
}
return isSuccessIterationForAll;
}
/**
* Checks if the passing <code>File</code> instance is associated with <code>String</code>
* review ID.
*
* @param reviewId the review ID.
* @param reviewFile the review file to be checked.
* @return <code>true</code> if the code>File</code> instance is associated with
* <code>String</code>.
* @throws ReviewException if problems occur during file reading process.
*/
static boolean isReviewIdAssociatedFile(String reviewId, File reviewFile)
throws ReviewException {
Review review;
try {
XMLInputFactory xmlif = XMLInputFactory.newInstance();
xmlif.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, Boolean.TRUE);
xmlif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
xmlif.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE);
XMLStreamReader reader = xmlif.createXMLStreamReader(reviewFile.getAbsolutePath(),
new FileInputStream(reviewFile));
review = StaxReviewXmlUtil.parseReviewFile(reader);
}
catch (XMLStreamException e) {
throw new ReviewException("XMLStreamException: " + e.getMessage(), e);
}
catch (FileNotFoundException e) {
throw new ReviewException("FileNotFoundException: " + e.getMessage(), e);
}
String reviewIdName = review.getId();
return (reviewIdName != null && reviewIdName.equals(reviewId));
}
/**
* Removes all <code>IFile</code> instances.
*
* @param iFiles the array of the <code>IFile</code> instances.
* @throws ReviewException if problems occur during the file deletion.
*/
static void remove(IFile[] iFiles) throws ReviewException {
for (int i = 0; i < iFiles.length; i++) {
try {
iFiles[i].delete(true, false, null);
}
catch (CoreException e) {
throw new ReviewException(e.getMessage());
}
}
}
}