blob: a147be5c533ec18a85f99cfd9d5461d7863ce145 [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.tasks.impl;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonSyntaxException;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.tasks.Task;
import com.intellij.tasks.TaskRepository;
import com.intellij.tasks.TaskState;
import com.intellij.tasks.impl.httpclient.ResponseUtil;
import com.intellij.util.text.SyncDateFormat;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.http.HttpResponse;
import org.apache.http.protocol.HTTP;
import org.jdom.Element;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Dmitry Avdeev
*/
public class TaskUtil {
private static SyncDateFormat ISO8601_DATE_FORMAT = new SyncDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"));
static {
// Use UTC time zone by default (for formatting)
ISO8601_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
}
// Almost ISO-8601 strict except date parts may be separated by '/'
// and date only also allowed just in case
private static Pattern ISO8601_DATE_PATTERN = Pattern.compile(
"(\\d{4}[/-]\\d{2}[/-]\\d{2})" + // date (1)
"(?:[ T]" +
"(\\d{2}:\\d{2}:\\d{2})(.\\d{3,})?" + // optional time (2) and milliseconds (3)
"(?:\\s?" +
"([+-]\\d{2}:\\d{2}|[+-]\\d{4}|[+-]\\d{2}|Z)" + // optional timezone info (4), if time is also present
")?)?"
);
private TaskUtil() {
// empty
}
public static String formatTask(@NotNull Task task, String format) {
return format
.replace("{id}", task.getId())
.replace("{number}", task.getNumber())
.replace("{project}", StringUtil.notNullize(task.getProject()))
.replace("{summary}", task.getSummary());
}
@Nullable
public static String getChangeListComment(Task task) {
final TaskRepository repository = task.getRepository();
if (repository == null || !repository.isShouldFormatCommitMessage()) {
return null;
}
return formatTask(task, repository.getCommitMessageFormat());
}
public static String getTrimmedSummary(Task task) {
String text;
if (task.isIssue()) {
text = task.getId() + ": " + task.getSummary();
}
else {
text = task.getSummary();
}
return StringUtil.first(text, 60, true);
}
@Nullable
public static Date parseDate(@NotNull String s) {
// SimpleDateFormat prior JDK7 doesn't support 'X' specifier for ISO 8601 timezone format.
// Because some bug trackers and task servers e.g. send dates ending with 'Z' (that stands for UTC),
// dates should be preprocessed before parsing.
Matcher m = ISO8601_DATE_PATTERN.matcher(s);
if (!m.matches()) {
return null;
}
String datePart = m.group(1).replace('/', '-');
String timePart = m.group(2);
if (timePart == null) {
timePart = "00:00:00";
}
String milliseconds = m.group(3);
milliseconds = milliseconds == null ? "000" : milliseconds.substring(1, 4);
String timezone = m.group(4);
if (timezone == null || timezone.equals("Z")) {
timezone = "+0000";
}
else if (timezone.length() == 3) {
// [+-]HH
timezone += "00";
}
else if (timezone.length() == 6) {
// [+-]HH:MM
timezone = timezone.substring(0, 3) + timezone.substring(4, 6);
}
String canonicalForm = String.format("%sT%s.%s%s", datePart, timePart, milliseconds, timezone);
try {
return ISO8601_DATE_FORMAT.parse(canonicalForm);
}
catch (ParseException e) {
return null;
}
}
public static String formatDate(@NotNull Date date) {
return ISO8601_DATE_FORMAT.format(date);
}
/**
* {@link Task#equals(Object)} implementation compares tasks by their unique IDs only.
* This method should be used when full comparison is necessary.
*/
public static boolean tasksEqual(@NotNull Task t1, @NotNull Task t2) {
if (!t1.getId().equals(t2.getId())) return false;
if (!t1.getSummary().equals(t2.getSummary())) return false;
if (t1.isClosed() != t2.isClosed()) return false;
if (t1.isIssue() != t2.isIssue()) return false;
if (!Comparing.equal(t1.getState(), t2.getState())) return false;
if (!Comparing.equal(t1.getType(), t2.getType())) return false;
if (!Comparing.equal(t1.getDescription(), t2.getDescription())) return false;
if (!Comparing.equal(t1.getCreated(), t2.getCreated())) return false;
if (!Comparing.equal(t1.getUpdated(), t2.getUpdated())) return false;
if (!Comparing.equal(t1.getIssueUrl(), t2.getIssueUrl())) return false;
if (!Comparing.equal(t1.getComments(), t2.getComments())) return false;
if (!Comparing.equal(t1.getIcon(), t2.getIcon())) return false;
if (!Comparing.equal(t1.getCustomIcon(), t2.getCustomIcon())) return false;
return Comparing.equal(t1.getRepository(), t2.getRepository());
}
public static boolean tasksEqual(@NotNull List<? extends Task> tasks1, @NotNull List<? extends Task> tasks2) {
if (tasks1.size() != tasks2.size()) return false;
for (int i = 0; i < tasks1.size(); i++) {
if (!tasksEqual(tasks1.get(i), tasks2.get(i))) {
return false;
}
}
return true;
}
public static boolean tasksEqual(@NotNull Task[] task1, @NotNull Task[] task2) {
return tasksEqual(Arrays.asList(task1), Arrays.asList(task2));
}
/**
* Print pretty-formatted XML to {@code logger}, if its level is DEBUG or below.
*/
public static void prettyFormatXmlToLog(@NotNull Logger logger, @NotNull Element element) {
if (logger.isDebugEnabled()) {
// alternatively
//new XMLOutputter(Format.getPrettyFormat()).outputString(root)
logger.debug("\n" + JDOMUtil.createOutputter("\n").outputString(element));
}
}
/**
* Parse and print pretty-formatted XML to {@code logger}, if its level is DEBUG or below.
*/
public static void prettyFormatXmlToLog(@NotNull Logger logger, @NotNull InputStream xml) {
if (logger.isDebugEnabled()) {
try {
logger.debug("\n" + JDOMUtil.createOutputter("\n").outputString(JDOMUtil.loadDocument(xml)));
}
catch (Exception e) {
logger.debug(e);
}
}
}
/**
* Parse and print pretty-formatted XML to {@code logger}, if its level is DEBUG or below.
*/
public static void prettyFormatXmlToLog(@NotNull Logger logger, @NotNull String xml) {
if (logger.isDebugEnabled()) {
try {
logger.debug("\n" + JDOMUtil.createOutputter("\n").outputString(JDOMUtil.loadDocument(xml)));
}
catch (Exception e) {
logger.debug(e);
}
}
}
/**
* Parse and print pretty-formatted Json to {@code logger}, if its level is DEBUG or below.
*/
public static void prettyFormatJsonToLog(@NotNull Logger logger, @NotNull String json) {
if (logger.isDebugEnabled()) {
try {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
logger.debug("\n" + gson.toJson(gson.fromJson(json, JsonElement.class)));
}
catch (JsonSyntaxException e) {
logger.debug("Malformed JSON\n" + json);
}
}
}
/**
* Parse and print pretty-formatted Json to {@code logger}, if its level is DEBUG or below.
*/
public static void prettyFormatJsonToLog(@NotNull Logger logger, @NotNull JsonElement json) {
if (logger.isDebugEnabled()) {
try {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
logger.debug("\n" + gson.toJson(json));
}
catch (JsonSyntaxException e) {
logger.debug("Malformed JSON\n" + json);
}
}
}
public static void prettyFormatResponseToLog(@NotNull Logger logger, @NotNull HttpMethod response) {
if (logger.isDebugEnabled() && response.hasBeenUsed()) {
try {
String content = ResponseUtil.getResponseContentAsString(response);
Header header = response.getRequestHeader(HTTP.CONTENT_TYPE);
String contentType = header == null ? "text/plain" : header.getElements()[0].getName().toLowerCase(Locale.ENGLISH);
if (contentType.contains("xml")) {
prettyFormatXmlToLog(logger, content);
}
else if (contentType.contains("json")) {
prettyFormatJsonToLog(logger, content);
}
else {
logger.debug(content);
}
}
catch (IOException e) {
logger.error(e);
}
}
}
public static void prettyFormatResponseToLog(@NotNull Logger logger, @NotNull HttpResponse response) {
if (logger.isDebugEnabled()) {
try {
String content = ResponseUtil.getResponseContentAsString(response);
org.apache.http.Header header = response.getEntity().getContentType();
String contentType = header == null ? "text/plain" : header.getElements()[0].getName().toLowerCase(Locale.ENGLISH);
if (contentType.contains("xml")) {
prettyFormatXmlToLog(logger, content);
}
else if (contentType.contains("json")) {
prettyFormatJsonToLog(logger, content);
}
else {
logger.debug(content);
}
}
catch (IOException e) {
logger.error(e);
}
}
}
/**
* Perform standard {@code application/x-www-urlencoded} translation for string {@code s}.
*
* @return urlencoded string
*/
@NotNull
public static String encodeUrl(@NotNull String s) {
try {
return URLEncoder.encode(s, CharsetToolkit.UTF8);
}
catch (UnsupportedEncodingException e) {
throw new AssertionError("UTF-8 is not supported");
}
}
@Contract("null, _ -> false")
public static boolean isStateSupported(@Nullable TaskRepository repository, @NotNull TaskState state) {
if (repository == null || !repository.isSupported(TaskRepository.STATE_UPDATING)) {
return false;
}
return repository.getRepositoryType().getPossibleTaskStates().contains(state);
}
}