PageRenderTime 59ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 1ms

/src/main/java/com/osf/jira/plugins/util/TextUtil.java

https://bitbucket.org/azhdanov/jiraworkplan
Java | 417 lines | 288 code | 30 blank | 99 comment | 89 complexity | d35cfac59165660654d5ae320f4c99cd MD5 | raw file
  1. /**
  2. * Copyright (c) 2008, Outsourcing Factory Inc.,
  3. * http://www.outsourcing-factory.com/
  4. *
  5. * All rights reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions are met:
  9. *
  10. * * Redistributions of source code must retain the above copyright notice,
  11. * this list of conditions and the following disclaimer.
  12. *
  13. * * Redistributions in binary form must reproduce the above copyright notice,
  14. * this list of conditions and the following disclaimer in the documentation
  15. * and/or other materials provided with the distribution.
  16. *
  17. * * Neither the name of the Outsourcing Factory Inc. nor the names of its
  18. * contributors may be used to endorse or promote products derived from this
  19. * software without specific prior written permission.
  20. *
  21. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  22. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  23. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  24. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
  25. * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  26. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  27. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  28. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  29. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  30. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  31. * POSSIBILITY OF SUCH DAMAGE.
  32. *
  33. */
  34. package com.osf.jira.plugins.util;
  35. import java.text.NumberFormat;
  36. import java.util.Collection;
  37. import java.util.HashMap;
  38. import java.util.Iterator;
  39. import java.util.List;
  40. import java.util.Map;
  41. import java.util.ResourceBundle;
  42. import java.util.Set;
  43. import org.apache.log4j.Logger;
  44. import org.apache.velocity.exception.VelocityException;
  45. import com.atlassian.core.util.DateUtils;
  46. import com.atlassian.jira.component.ComponentAccessor;
  47. import com.atlassian.jira.config.properties.ApplicationProperties;
  48. import com.atlassian.jira.datetime.DateTimeFormatter;
  49. import com.atlassian.jira.issue.CustomFieldManager;
  50. import com.atlassian.jira.issue.Issue;
  51. import com.atlassian.jira.issue.fields.AffectedVersionsSystemField;
  52. import com.atlassian.jira.issue.fields.AssigneeSystemField;
  53. import com.atlassian.jira.issue.fields.ComponentsSystemField;
  54. import com.atlassian.jira.issue.fields.CustomField;
  55. import com.atlassian.jira.issue.fields.Field;
  56. import com.atlassian.jira.issue.fields.FieldManager;
  57. import com.atlassian.jira.issue.fields.FixVersionsSystemField;
  58. import com.atlassian.jira.issue.fields.IssueLinksSystemField;
  59. import com.atlassian.jira.issue.fields.IssueTypeSystemField;
  60. import com.atlassian.jira.issue.fields.LabelsSystemField;
  61. import com.atlassian.jira.issue.fields.OriginalEstimateSystemField;
  62. import com.atlassian.jira.issue.fields.PrioritySystemField;
  63. import com.atlassian.jira.issue.fields.ProjectSystemField;
  64. import com.atlassian.jira.issue.fields.ResolutionSystemField;
  65. import com.atlassian.jira.issue.fields.SecurityLevelSystemField;
  66. import com.atlassian.jira.issue.fields.StatusSystemField;
  67. import com.atlassian.jira.issue.fields.TimeEstimateSystemField;
  68. import com.atlassian.jira.issue.fields.TimeSpentSystemField;
  69. import com.atlassian.jira.issue.fields.TimeTrackingSystemField;
  70. import com.atlassian.jira.issue.fields.layout.field.FieldLayoutItem;
  71. import com.atlassian.jira.issue.fields.layout.field.FieldLayoutManager;
  72. import com.atlassian.jira.issue.issuetype.IssueType;
  73. import com.atlassian.jira.issue.link.IssueLinkManager;
  74. import com.atlassian.jira.issue.link.IssueLinkType;
  75. import com.atlassian.jira.issue.link.LinkCollection;
  76. import com.atlassian.jira.ofbiz.OfBizValueWrapper;
  77. import com.atlassian.jira.security.JiraAuthenticationContext;
  78. import com.atlassian.jira.user.ApplicationUser;
  79. import com.atlassian.jira.util.I18nHelper;
  80. import com.atlassian.velocity.VelocityManager;
  81. import com.opensymphony.util.TextUtils;
  82. /**
  83. * @author Andriy Zhdanov
  84. */
  85. public class TextUtil {
  86. private static final Logger log = Logger.getLogger(TextUtil.class);
  87. private long secondsPerDay;
  88. private long secondsPerWeek;
  89. private ResourceBundle resourceBundle;
  90. private NumberFormat decimalFormat;
  91. private final VelocityManager velocityManager;
  92. private final FieldManager fieldManager;
  93. private final FieldLayoutManager fieldLayoutManager;
  94. private final CustomField epicLinkField;
  95. private static final String GH_PLUGIN_KEY = "com.pyxis.greenhopper.jira";
  96. private static final String EPIC_LINK_FIELD_ID = GH_PLUGIN_KEY + ":gh-epic-link";
  97. /**
  98. * Instantiate TextUtil object
  99. * @param i18nBean
  100. */
  101. public TextUtil(I18nHelper i18nBean) {
  102. ApplicationProperties ap = ComponentAccessor.getApplicationProperties();
  103. secondsPerDay = new Float(Float.valueOf(ap.getDefaultBackedString("jira.timetracking.hours.per.day")) * 3600).longValue();
  104. secondsPerWeek = new Float(Float.valueOf(ap.getDefaultBackedString("jira.timetracking.days.per.week")) * secondsPerDay).longValue();
  105. resourceBundle = i18nBean.getDefaultResourceBundle();
  106. decimalFormat = NumberFormat.getInstance(i18nBean.getLocale());
  107. velocityManager = ComponentAccessor.getVelocityManager();
  108. fieldManager = ComponentAccessor.getFieldManager();
  109. fieldLayoutManager = ComponentAccessor.getFieldLayoutManager();
  110. CustomFieldManager customFieldManager = ComponentAccessor.getCustomFieldManager();
  111. List<CustomField> customFields = customFieldManager.getCustomFieldObjects();
  112. CustomField epicLinkField = null;
  113. for (CustomField customField : customFields) {
  114. if (EPIC_LINK_FIELD_ID.equals(customField.getCustomFieldType().getKey())) {
  115. epicLinkField = customField;
  116. break;
  117. }
  118. }
  119. this.epicLinkField = epicLinkField;
  120. }
  121. /**
  122. * Format duration value
  123. * @param value
  124. * @return pretty formatted value, using Jira settings for hours in day, and days in week.
  125. */
  126. public String getPrettyDuration(long value)
  127. {
  128. return DateUtils.getDurationPrettySeconds(value, secondsPerDay, secondsPerWeek, resourceBundle);
  129. }
  130. /**
  131. * Format duration value in hours
  132. * @param value
  133. * @return value
  134. */
  135. public String getPrettyHours(long value)
  136. {
  137. return getHours(value) + "h";
  138. }
  139. /**
  140. * Format duration value in hours
  141. * @param value
  142. * @return pretty formatted value
  143. */
  144. public String getHours(long value)
  145. {
  146. return decimalFormat.format(((float)value) / 60 / 60);
  147. }
  148. /**
  149. * Get issue field value by field id for the issue.
  150. *
  151. * @param groupByFieldID
  152. * @param issue
  153. * @param dateTimeFormatter
  154. * @param req if to render html for timetracking field
  155. * @return String value concatenated for multi-select or null.
  156. */
  157. public String getFieldValue(String groupByFieldID, Issue issue,
  158. DateTimeFormatter dateTimeFormatter, String contextPath) {
  159. if (groupByFieldID == null) {
  160. return "";
  161. }
  162. Field groupByField = fieldManager.getField(groupByFieldID);
  163. if (groupByField == null) {
  164. log.error("Field '" + groupByFieldID + "' does not exist");
  165. return "";
  166. }
  167. // Set field value
  168. String fieldValue = null;
  169. if (groupByField instanceof CustomField) {
  170. CustomField customField = (CustomField) groupByField;
  171. IssueType issueType = issue.getIssueTypeObject();
  172. // Issue#439: Epic fields are not applied to sub-tasks
  173. if (customField.getCustomFieldType().getKey().startsWith(GH_PLUGIN_KEY)) {
  174. issue = getEpic(issue, customField);
  175. }
  176. Object value = customField.getValue(issue);
  177. if (value != null) {
  178. FieldLayoutItem fieldLayoutItem = fieldLayoutManager
  179. .getFieldLayout(issue.getProjectObject(), issueType.getId())
  180. .getFieldLayoutItem(groupByFieldID);
  181. if (fieldLayoutItem != null) {
  182. fieldValue = customField.getViewHtml(fieldLayoutItem, null, issue);
  183. } else {
  184. fieldValue = TextUtils.plainTextToHtml(fieldValue);
  185. }
  186. /*
  187. if (groupByField instanceof CustomFieldStattable) {
  188. StatisticsMapper sm = ((CustomFieldStattable)
  189. groupByField).getStatisticsMapper((CustomField) groupByField);
  190. fieldValue = sm.getValueFromLuceneField(value.toString()).toString();
  191. }
  192. if (value instanceof List) {
  193. fieldValue = getMultiValue((List) value);
  194. } else if (value instanceof Date ) {
  195. fieldValue = dateTimeFormatter.format((Date) value);
  196. } else if (value instanceof User){
  197. fieldValue = ((User) value).getDisplayName();
  198. } else if (value instanceof Map) {
  199. fieldValue = getMultiValue((Map) value);
  200. } else {
  201. fieldValue = value.toString();
  202. }
  203. */
  204. }
  205. } else if (groupByField instanceof ComponentsSystemField) {
  206. /*
  207. * Implementation to handle GroupBy Component. Issue TIME-54.
  208. * Caveat: When there are multiple components assigned to one
  209. * issue, the component names are concatenated and the grouping
  210. * is done by the concatenated string. The issue isn't
  211. * counted/grouped for each component.
  212. */
  213. fieldValue = getMultiValue(issue.getComponents());
  214. } else if (groupByField instanceof AffectedVersionsSystemField) {
  215. fieldValue = getMultiValue(issue.getAffectedVersions());
  216. } else if (groupByField instanceof FixVersionsSystemField) {
  217. fieldValue = getMultiValue(issue.getFixVersions());
  218. } else if (groupByField instanceof IssueTypeSystemField) {
  219. fieldValue = issue.getIssueTypeObject().getNameTranslation();
  220. } else if (groupByField instanceof LabelsSystemField) {
  221. fieldValue = getMultiValue(issue.getLabels());
  222. } else if (groupByField instanceof StatusSystemField) {
  223. fieldValue = issue.getStatusObject().getName();
  224. } else if (groupByField instanceof IssueLinksSystemField) {
  225. fieldValue = getIssueLinks(issue);
  226. } else if (groupByField instanceof SecurityLevelSystemField) {
  227. fieldValue = issue.getSecurityLevel().getString("name");
  228. } else if (groupByField instanceof TimeTrackingSystemField) {
  229. Long timeSpent = getIssueTimeSpent(issue);
  230. Long remaining = getIssueRemainingEstimate(issue);
  231. if (contextPath != null) {
  232. Map<String, Object> params = new HashMap<String, Object>();
  233. params.put("timeSpent", getFieldName("timespent") + " - " + getPrettyHours(timeSpent));
  234. params.put("timeSpentRate", Math.round(timeSpent) * 100 / (timeSpent + remaining));
  235. params.put("remaining", getFieldName("timeestimate") + " - " + getPrettyHours(remaining));
  236. params.put("remainingRate", Math.round(remaining) * 100 / (timeSpent + remaining));
  237. params.put("contextPath", contextPath);
  238. try {
  239. fieldValue = velocityManager.getBody("/templates/timesheetreport/", "timetracking.vm", params);
  240. } catch (VelocityException e) {
  241. log.warn("Error while rendering timetracking field", e);
  242. }
  243. } else {
  244. fieldValue = getPrettyHours(timeSpent) +
  245. "/" + getPrettyHours(remaining);
  246. }
  247. } else if (groupByField instanceof TimeSpentSystemField) {
  248. long timeSpent = getIssueTimeSpent(issue);
  249. fieldValue = getPrettyHours(timeSpent);
  250. } else if (groupByField instanceof TimeEstimateSystemField) {
  251. long remaining = getIssueRemainingEstimate(issue);
  252. fieldValue = getPrettyHours(remaining);
  253. } else if (groupByField instanceof OriginalEstimateSystemField && issue.getOriginalEstimate() != null) {
  254. long original = getIssueOriginalEstimate(issue);
  255. fieldValue = getPrettyHours(original);
  256. } else if (groupByField instanceof ProjectSystemField) {
  257. fieldValue = issue.getProjectObject().getName();
  258. } else if (groupByField instanceof PrioritySystemField) {
  259. fieldValue = issue.getPriorityObject().getName();
  260. } else if (groupByField instanceof ResolutionSystemField && issue.getResolutionObject() != null) {
  261. fieldValue = issue.getResolutionObject().getName();
  262. } else if (groupByField instanceof AssigneeSystemField) {
  263. ApplicationUser assignee = issue.getAssignee();
  264. if (assignee != null) {
  265. fieldValue = assignee.getDisplayName();
  266. } // else "NoValueForFieldOnIssue";
  267. } else {
  268. // TODO Couldn't find an easy way to get each fields value as
  269. // string. Workaround.
  270. try {
  271. fieldValue = issue.getString(groupByFieldID);
  272. } catch (RuntimeException e) {
  273. fieldValue = "FieldTypeValueNotApplicableForGrouping";
  274. }
  275. }
  276. // need a string as reference element in map for grouping
  277. if (fieldValue == null || fieldValue.trim().length() == 0) {
  278. fieldValue = "NoValueForFieldOnIssue";
  279. }
  280. return fieldValue;
  281. }
  282. private Issue getEpic(Issue issue, CustomField customField) {
  283. if (issue.isSubTask()) {
  284. return getEpic(issue.getParentObject(), customField);
  285. } else if (!EPIC_LINK_FIELD_ID.equals(customField.getCustomFieldType().getKey()) &&
  286. !isEpic(issue)) {
  287. if (epicLinkField != null) {
  288. Issue epic = (Issue) issue.getCustomFieldValue(epicLinkField);
  289. if (epic != null) {
  290. return epic;
  291. }
  292. }
  293. }
  294. return issue;
  295. }
  296. public CustomField getEpicLinkField() {
  297. return epicLinkField;
  298. }
  299. private boolean isEpic(Issue issue) {
  300. return "Epic".equals(issue.getIssueTypeObject().getName());
  301. }
  302. private long getIssueTimeSpent(Issue issue) {
  303. Long time = issue.getTimeSpent();
  304. if (time == null) {
  305. time = 0L;
  306. }
  307. return time;
  308. }
  309. private long getIssueRemainingEstimate(Issue issue) {
  310. Long time = issue.getEstimate();
  311. if (time == null) {
  312. time = 0L;
  313. }
  314. return time;
  315. }
  316. private long getIssueOriginalEstimate(Issue issue) {
  317. Long time = issue.getOriginalEstimate();
  318. if (time == null) {
  319. time = 0L;
  320. }
  321. return time;
  322. }
  323. private static String getIssueLinks(Issue issue) {
  324. StringBuffer result = new StringBuffer();
  325. IssueLinkManager issueLinkManager = ComponentAccessor.getIssueLinkManager();
  326. JiraAuthenticationContext authenticationContext = ComponentAccessor.getJiraAuthenticationContext();
  327. ApplicationUser remoteUser = authenticationContext.getLoggedInUser();
  328. LinkCollection linkCollection = issueLinkManager.getLinkCollection(issue, remoteUser);
  329. Set<IssueLinkType> linkTypes = linkCollection.getLinkTypes();
  330. for (IssueLinkType linkType : linkTypes) {
  331. appendLinkedIssues(linkCollection.getOutwardIssues(linkType.getName()), linkType.getOutward(), result);
  332. appendLinkedIssues(linkCollection.getInwardIssues(linkType.getName()), linkType.getInward(), result);
  333. }
  334. return result.toString();
  335. }
  336. private static void appendLinkedIssues(List<Issue> linkedIssues, String linkName, StringBuffer result) {
  337. if (linkedIssues != null) {
  338. result.append(TextUtils.plainTextToHtml(linkName));
  339. result.append(": ");
  340. for (int i = 0; i < linkedIssues.size(); i++) {
  341. Issue linkedIssue = linkedIssues.get(i);
  342. result.append(linkedIssue.getKey());
  343. if (i + 1 < linkedIssues.size()) {
  344. result.append(", ");
  345. }
  346. }
  347. result.append("<br/>");
  348. }
  349. }
  350. private static String getMultiValue(Collection<? extends Object> values) {
  351. StringBuffer fieldValue = new StringBuffer();
  352. for (Iterator<? extends Object> i = values.iterator(); i.hasNext();) {
  353. Object o = i.next();
  354. String value = null;
  355. if (o instanceof Map) {
  356. Map<String, Object> map= (Map<String, Object>) o;
  357. // do not check if (map.containsKey("name")) intentionally
  358. // for better diagnosability
  359. value = (String) map.get("name");
  360. } else if (o instanceof OfBizValueWrapper) {
  361. OfBizValueWrapper map= (OfBizValueWrapper) o;
  362. value = map.getString("name");
  363. } else if (o instanceof ApplicationUser){
  364. value = ((ApplicationUser) o).getDisplayName();
  365. } else if (o != null) {
  366. value = o.toString();
  367. }
  368. if (value != null) {
  369. fieldValue.append(value);
  370. fieldValue.append(", ");
  371. }
  372. }
  373. return fieldValue.length() > 0 ? fieldValue.substring(0, fieldValue.length() - 2) : "";
  374. }
  375. /**
  376. * @deprecated since JIRA 6 it's not needed
  377. */
  378. @Deprecated
  379. public static String getUnquotedString(String s) {
  380. return s; // JRA-31905: no need to escape anything anymore
  381. }
  382. public static String getFieldName(String fieldID) {
  383. FieldManager fieldManager = ComponentAccessor.getFieldManager();
  384. Field groupByField = fieldManager.getField(fieldID);
  385. return groupByField != null ? groupByField.getName() : "N/A";
  386. }
  387. }