package com.ghostflying.portalwaitinglist.util;
import android.util.Base64;
import com.ghostflying.portalwaitinglist.model.EditEvent;
import com.ghostflying.portalwaitinglist.model.InvalidEvent;
import com.ghostflying.portalwaitinglist.model.Message;
import com.ghostflying.portalwaitinglist.model.PortalDetail;
import com.ghostflying.portalwaitinglist.model.PortalEvent;
import com.ghostflying.portalwaitinglist.model.SubmissionEvent;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
/**
* Created by Ghost on 2014/12/4.
*/
public class MailProcessUtil {
private static MailProcessUtil instance;
static final String PARSE_ERROR_TEXT = "Parse error, maybe the mail is too old to contain this part.";
int acceptedCount;
int rejectedCount;
int proposedCount;
private MailProcessUtil(){}
public static MailProcessUtil getInstance(){
if(instance == null){
synchronized (MailProcessUtil.class){
if (instance == null){
instance = new MailProcessUtil();
}
}
}
return instance;
}
/**
* Convert messages to events.
* @param messages the origin messages.
* @return the converted events.
*/
public ArrayList<PortalEvent> analysisMessages(ArrayList<Message> messages){
ArrayList<PortalEvent> portalEvents = new ArrayList<>();
acceptedCount = 0;
rejectedCount = 0;
proposedCount = 0;
for (Message message : messages){
PortalEvent portalEvent = analysisMessage(message);
if (portalEvent != null)
portalEvents.add(portalEvent);
}
return portalEvents;
}
/**
* Convert message to event.
* @param message the origin message.
* @return the converted event.
*/
private PortalEvent analysisMessage(Message message){
// Try to match regex.
String subject = message.getSubject();
RegexUtil util = RegexUtil.getInstance();
if (util.isFound(RegexUtil.PORTAL_SUBMISSION, subject)){
proposedCount ++;
return new SubmissionEvent(util.getMatchedStr().trim(),
PortalEvent.OperationResult.PROPOSED,
message.getDate(),
message.getId(),
getImageUrl(message.getMessageHtml()));
}
if (util.isFound(RegexUtil.PORTAL_EDIT, subject)){
proposedCount ++;
return new EditEvent(util.getMatchedStr().trim(),
PortalEvent.OperationResult.PROPOSED, message.getDate(), message.getId());
}
if (util.isFound(RegexUtil.INVALID_REPORT, subject)){
proposedCount ++;
return new InvalidEvent(util.getMatchedStr().trim(),
PortalEvent.OperationResult.PROPOSED,
message.getDate(),
message.getId(),
getImageUrl(message.getMessageHtml()));
}
if (util.isFound(RegexUtil.PORTAL_SUBMISSION_PASSED, subject)){
acceptedCount ++;
return new SubmissionEvent(util.getMatchedStr().trim(),
PortalEvent.OperationResult.ACCEPTED,
message.getDate(),
message.getId(),
getImageUrl(message.getMessageHtml()),
getPortalAddress(message.getMessageHtml()),
getPortalAddressUrl(message.getMessageHtml()));
}
if (util.isFound(RegexUtil.PORTAL_SUBMISSION_REJECTED, subject)){
rejectedCount ++;
return new SubmissionEvent(util.getMatchedStr().trim(),
PortalEvent.OperationResult.REJECTED,
message.getDate(),
message.getId(),
getImageUrl(message.getMessageHtml()));
}
if (util.isFound(RegexUtil.PORTAL_SUBMISSION_DUPLICATE, subject)){
rejectedCount ++;
return new SubmissionEvent(util.getMatchedStr().trim(),
PortalEvent.OperationResult.DUPLICATE,
message.getDate(),
message.getId(),
getImageUrl(message.getMessageHtml()));
}
if (util.isFound(RegexUtil.PORTAL_EDIT_PASSED, subject)){
acceptedCount ++;
return new EditEvent(util.getMatchedStr().trim(),
PortalEvent.OperationResult.ACCEPTED,
message.getDate(), message.getId(),
getPortalAddress(message.getMessageHtml()),
getPortalAddressUrl(message.getMessageHtml()));
}
if (util.isFound(RegexUtil.PORTAL_EDIT_REJECTED, subject)){
rejectedCount ++;
return new EditEvent(util.getMatchedStr().trim(),
PortalEvent.OperationResult.REJECTED,
message.getDate(), message.getId(),
getPortalAddress(message.getMessageHtml()),
getPortalAddressUrl(message.getMessageHtml()));
}
return null;
}
private String getImageUrl(String html){
String decodeStr = decodeMailHtml(html);
if (RegexUtil.getInstance().isFound(RegexUtil.IMG_URL, decodeStr))
return RegexUtil.getInstance().getMatchedStr();
return PARSE_ERROR_TEXT;
}
private String getPortalAddress(String html){
String decodeStr = decodeMailHtml(html);
if (RegexUtil.getInstance().isFound(RegexUtil.ADDRESS, decodeStr))
return RegexUtil.getInstance().getMatchedStr();
return PARSE_ERROR_TEXT;
}
private String getPortalAddressUrl(String html){
String decodeStr = decodeMailHtml(html);
if (RegexUtil.getInstance().isFound(RegexUtil.ADDRESS_URL, decodeStr))
return RegexUtil.getInstance().getMatchedStr();
return PARSE_ERROR_TEXT;
}
private String decodeMailHtml(String html) {
byte[] decodeBytes = Base64.decode(html, Base64.URL_SAFE);
return new String(decodeBytes);
}
/**
* Merge new events to exist portal detail or create new one.
* To improve performance, the method is designed to work fine only if
* the events is newer than each one in origin and the event in events is in
* asc order by date.
* @param origin exist portal detail list.
* @param events events need to be merged.
* @return the merged portal detail list.
*/
public List<PortalDetail> mergeEvents(List<PortalDetail> origin, List<PortalEvent> events){
for (PortalEvent event : events){
PortalDetail existDetail = findExistDetail(origin, event);
if (existDetail == null){
PortalDetail newDetail = new PortalDetail(event);
origin.add(newDetail);
}
else {
existDetail.addEvent(event);
// sort the event list.
// Collections.sort(existDetail.getEvents());
}
}
return origin;
}
/**
* Find the exist portal detail has the same name with new event name.
* To improve performance, the method is designed to work fine
* only if the new event is newer than any one in details.
* @param details exist portal detail list.
* @param event new event.
* @return the portal detail if exists, or null if it does not exist.
*/
private PortalDetail findExistDetail(List<PortalDetail> details, PortalEvent event){
for (PortalDetail detail : details){
if (detail.getName().equalsIgnoreCase(event.getPortalName())){
// if event is Edit, only check name.
// it may lead some mistakes but have no ideas now.
if (event instanceof EditEvent)
return detail;
else {
String url = detail.getImageUrl();
// for submission event, must check the image url of the portal.
// this may have some mistakes for the lack of some mails, ignored.
if (url != null &&
url.equalsIgnoreCase(((SubmissionEvent)event).getPortalImageUrl())){
return detail;
}
}
}
}
return null;
}
/**
* To sort and filter origin and store result to edit.
* @param typeFilterMethod the type filter mehod to use.
* @param resultFilterMethod the result filter method to use.
* @param sortOrder the sort order to use.
* @param origin the origin list.
* @param edit the result list.
*/
public void filterAndSort(SettingUtil.TypeFilterMethod typeFilterMethod,
SettingUtil.ResultFilterMethod resultFilterMethod,
SettingUtil.SortOrder sortOrder,
List<PortalDetail> origin,
List<PortalDetail> edit){
filterPortalDetails(typeFilterMethod, resultFilterMethod, origin, edit);
sortPortalDetails(sortOrder, edit);
}
/**
* Filter the list of portal details.
* @param typeFilterMethod the type filter method to use.
* @param resultFilterMethod the result filter method to use.
* @param origin the origin list to be filter.
* @param filtered the filtered list.
*/
private void filterPortalDetails(SettingUtil.TypeFilterMethod typeFilterMethod,
SettingUtil.ResultFilterMethod resultFilterMethod,
List<PortalDetail> origin,
List<PortalDetail> filtered){
DoResultCheck doResultCheck;
DoTypeCheck doTypeCheck;
switch (resultFilterMethod){
case WAITING:
doResultCheck = new DoWaitingCheck();
break;
case ACCEPTED:
doResultCheck = new DoAcceptedCheck();
break;
case REJECTED:
doResultCheck = new DoRejectedCheck();
break;
case EVERYTHING:
default:
doResultCheck = new DoEveryThingCheck();
}
switch (typeFilterMethod){
case ALL:
doTypeCheck = new DoAllCheck();
break;
case SUBMISSION:
doTypeCheck = new DoSubmissionCheck();
break;
case EDIT:
doTypeCheck = new DoEditCheck();
break;
default:
doTypeCheck = new DoAllCheck();
}
filtered.clear();
for (PortalDetail detail : origin){
if (doTypeCheck.checkFilterMethod(detail)
&& doResultCheck.checkFilterMethod(detail))
filtered.add(detail);
}
}
/**
* Sort the list of portal details.
* @param sortOrder the order set.
* @param portalDetails the sorted list.
*/
public void sortPortalDetails(SettingUtil.SortOrder sortOrder, List<PortalDetail> portalDetails){
switch (sortOrder){
case LAST_DATE_ASC:
Collections.sort(portalDetails);
break;
case LAST_DATE_DESC:
Collections.sort(portalDetails, Collections.reverseOrder());
break;
case SMART_ORDER:
Collections.sort(portalDetails, new Comparator<PortalDetail>() {
@Override
public int compare(PortalDetail lhs, PortalDetail rhs) {
int lhsPri = lhs.getOrderPrior();
int rhsPri = rhs.getOrderPrior();
if (lhsPri == rhsPri){
// asc for waiting portal, desc for others
// if if inverse waiting in smart set to true
// all is desc
if (lhsPri == PortalDetail.PRIORITY_WAITING_FOR_REVIEW
&& !SettingUtil.getIfInverseWaitingInSmart())
return lhs.compareTo(rhs);
else
return rhs.compareTo(lhs);
}
// sort by the priority of portal
else
return rhsPri - lhsPri;
}
});
break;
case ALPHABETICAL:
// get the comparator by locale
final Comparator comparator;
if (SettingUtil.getForceChinese()){
comparator = Collator.getInstance(Locale.CHINA);
}
else {
comparator = Collator.getInstance();
}
Collections.sort(portalDetails, new Comparator<PortalDetail>() {
@Override
public int compare(PortalDetail lhs, PortalDetail rhs) {
return comparator.compare(lhs.getName(), rhs.getName());
}
});
break;
case PROPOSED_DATE_ASC:
Collections.sort(portalDetails, new Comparator<PortalDetail>() {
@Override
public int compare(PortalDetail lhs, PortalDetail rhs) {
return lhs.getLastProposedUpdated()
.compareTo(rhs.getLastProposedUpdated());
}
});
break;
case PROPOSED_DATE_DESC:
Collections.sort(portalDetails, new Comparator<PortalDetail>() {
@Override
public int compare(PortalDetail lhs, PortalDetail rhs) {
return rhs.getLastProposedUpdated()
.compareTo(lhs.getLastProposedUpdated());
}
});
break;
}
}
/**
* Get the counts for each filter method.
* @param details the total details.
* @return the counts.
* the item 0 is the total count,
* the item 1 is the accepted count,
* the item 2 is the rejected count,
* the item 3 is the waiting count.
* the item 4 is the submission count.
* the item 5 is the edit count.
*/
public int[] getCounts(List<PortalDetail> details) {
int[] counts = new int[6];
for (PortalDetail detail : details){
if (detail.isEverAccepted())
counts[1]++;
else if (detail.isEverRejected())
counts[2]++;
if (detail.hasSubmission())
counts[4]++;
else
counts[5]++;
}
counts[0] = details.size();
counts[3] = counts[0] - counts[1] - counts[2];
return counts;
}
/**
* Get the counts in last process action.
* @return the array of counts, int order proposed, accepted, rejected.
*/
public int[] getEventCountsInLastProcess(){
return new int[]{
proposedCount,
acceptedCount,
rejectedCount
};
}
/**
* a list of classes to do check in filter
*/
private abstract class DoCheck {
public abstract boolean checkFilterMethod(PortalDetail detail);
}
private abstract class DoResultCheck extends DoCheck{
}
private abstract class DoTypeCheck extends DoCheck{
}
private class DoEveryThingCheck extends DoResultCheck {
@Override
public boolean checkFilterMethod(PortalDetail detail) {
return true;
}
}
private class DoAcceptedCheck extends DoResultCheck {
@Override
public boolean checkFilterMethod(PortalDetail detail) {
return detail.isEverAccepted();
}
}
private class DoRejectedCheck extends DoResultCheck {
@Override
public boolean checkFilterMethod(PortalDetail detail) {
return detail.isEverRejected();
}
}
private class DoWaitingCheck extends DoResultCheck{
@Override
public boolean checkFilterMethod(PortalDetail detail) {
return !detail.isReviewed();
}
}
private class DoAllCheck extends DoTypeCheck{
@Override
public boolean checkFilterMethod(PortalDetail detail) {
return true;
}
}
private class DoSubmissionCheck extends DoTypeCheck{
@Override
public boolean checkFilterMethod(PortalDetail detail) {
return detail.hasSubmission();
}
}
private class DoEditCheck extends DoTypeCheck{
@Override
public boolean checkFilterMethod(PortalDetail detail) {
return detail.hasEdit();
}
}
}