PageRenderTime 42ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/src/com/atlassian/uwc/hierarchies/SmfHierarchy.java

https://bitbucket.org/dodok1/uwc
Java | 301 lines | 223 code | 33 blank | 45 comment | 51 complexity | 92435fddc3156c773c54ebfa24ca02a2 MD5 | raw file
  1. package com.atlassian.uwc.hierarchies;
  2. import java.io.FileNotFoundException;
  3. import java.io.IOException;
  4. import java.text.DateFormat;
  5. import java.text.SimpleDateFormat;
  6. import java.util.Collection;
  7. import java.util.Comparator;
  8. import java.util.Date;
  9. import java.util.HashMap;
  10. import java.util.Iterator;
  11. import java.util.Properties;
  12. import java.util.Set;
  13. import java.util.Vector;
  14. import java.util.regex.Matcher;
  15. import java.util.regex.Pattern;
  16. import org.apache.commons.lang.StringEscapeUtils;
  17. import com.atlassian.uwc.ui.Page;
  18. public class SmfHierarchy extends MetaHierarchy {
  19. private static final String PROPKEY_COMMENTS = "reply-comments";
  20. private static final String PROPKEY_HIERARCHYCOMPARATOR = "hierarchy-child-comparator";
  21. private static final String TITLE_DELIM = " - ";
  22. private static final String DEFAULT_DATEFORMAT = "yyyy-MM-dd hmma";
  23. private static final String DEFAULT_EMPTY_TIME = "1";
  24. private static final String DEFAULT_EMPTY_NAME = "No Name";
  25. private static final String DEFAULT_EMPTY_TITLE = "No Title";
  26. public static final String DEFAULT_ROOTPAGENAME = "Home";
  27. @Override
  28. public HierarchyNode buildHierarchy(Collection<Page> pages) {
  29. HierarchyNode root = super.buildHierarchy(pages);
  30. handleComments(root);
  31. return root;
  32. }
  33. @Override
  34. protected HierarchyNode buildRelationships(Page page, HierarchyNode root) {
  35. assignRootPage(root);
  36. log.debug("page: " + page.getName());
  37. try {
  38. Properties meta = getMeta(page);
  39. String ancestorString = meta.getProperty("ancestors", "");
  40. if ("null".equals(ancestorString)) ancestorString = null;
  41. HierarchyNode node = root.getChildren().iterator().next(); //first child = Home. root will be ignored by engine.
  42. if (ancestorString != null && !"".equals(ancestorString)) { //set up ancestors
  43. String[] ancestors = ancestorString.split(":");
  44. for (int i = 0; i < ancestors.length; i++) {
  45. String ancestor = ancestors[i];
  46. node = buildNode(node, ancestor);
  47. }
  48. }
  49. node = buildNode(node, createNodeId(meta.getProperty("id"), meta.getProperty("type")), page);
  50. convertPagename(page, meta);
  51. node.setName(page.getName());
  52. } catch (FileNotFoundException e) {
  53. String error = "Meta file for page does not exist: " + getMetaPath(page);
  54. log.warn(error);
  55. } catch (IOException e) {
  56. String error = "Problem opening meta page: " + getMetaPath(page);
  57. log.warn(error);
  58. e.printStackTrace();
  59. }
  60. return root;
  61. }
  62. protected void init() { //called from MetaHierarchy.buildHierarchy
  63. super.init();
  64. clearCollisions();
  65. }
  66. /**
  67. * contains already used titles, so we can detect collisions
  68. */
  69. HashMap<String,String> collisions = null;
  70. /**
  71. * updates the page title with the info from the meta properties.
  72. * If the page title is not a valid Confluence title, it removes
  73. * any disallowed characters. It also checks for namespace collisions
  74. * and updates the page title with the username and timestamp if there
  75. * is one. The timestamp is controlled with the converter property "title-date-format".
  76. * @param page
  77. * @param meta
  78. * @return
  79. */
  80. protected String convertPagename(Page page, Properties meta) {
  81. String origTitle = meta.getProperty("title", DEFAULT_EMPTY_TITLE);
  82. String title = removeIllegalTitleChars(origTitle);
  83. if ("".equals(title)) title = DEFAULT_EMPTY_TITLE;
  84. //namespace collisions
  85. if (getCollisions().containsKey(title)) {
  86. String origName = meta.getProperty("username", DEFAULT_EMPTY_NAME);
  87. String name = removeIllegalTitleChars(origName);
  88. String type = meta.getProperty("type", "top");
  89. if (type.equals("top") || type.equals("re")) {
  90. String time = meta.getProperty("time", DEFAULT_EMPTY_TIME);
  91. String formattedTime = removeIllegalTitleChars(formatTime(time));
  92. if (!"".equals(formattedTime)) formattedTime = TITLE_DELIM + formattedTime;
  93. title += TITLE_DELIM + name + formattedTime;
  94. }
  95. //unlikely, but possible
  96. int index = 2;
  97. String current = title;
  98. while (getCollisions().containsKey(title)) {
  99. title = current + TITLE_DELIM + "No." + index++;
  100. }
  101. }
  102. getCollisions().put(title, "");
  103. //set the page name
  104. page.setName(title);
  105. return page.getName();
  106. }
  107. /**
  108. * @param time seconds since the epoch for the timestamp we are trying to format
  109. * @return title friendly formatted time (using either the converter property title-date-format
  110. * or the DEFAULT_DATEFORMAT. Date format should be formatted as described in SimpleDateFormat
  111. * @see SimpleDateFormat
  112. */
  113. protected String formatTime(String time) {
  114. //get format from converter properties
  115. String format = this.properties.getProperty("title-date-format", DEFAULT_DATEFORMAT);
  116. //format time
  117. long seconds;
  118. try {
  119. seconds = Long.parseLong(time);
  120. } catch (Exception e) {
  121. return ""; //can't parse time
  122. }
  123. long milli = seconds * 1000;
  124. Date date = new Date(milli);
  125. DateFormat dateFormat = null;
  126. try {
  127. dateFormat = new SimpleDateFormat(format);
  128. } catch (IllegalArgumentException e) {
  129. log.error("Custom date format is not a valid SimpleDateFormat: " + format
  130. + ". Using default format instead: " + DEFAULT_DATEFORMAT);
  131. dateFormat = new SimpleDateFormat(DEFAULT_DATEFORMAT);
  132. }
  133. return (dateFormat.format(date));
  134. }
  135. protected static String removeIllegalTitleChars(String input) {
  136. //html entities
  137. input = StringEscapeUtils.unescapeHtml(input);
  138. //illegal characters
  139. input = input.replaceAll("^[$~]", ""); //starting at title with ~ or $ is illegal
  140. input = input.replaceAll("^\\.\\.", "");//starting a title with .. is illegal
  141. //conf illegal chars
  142. input = input.replaceAll("[:;{}\\[\\]<>()@/\\\\|^#]", "");
  143. input = input.trim();
  144. return input;
  145. }
  146. private HashMap<String,String> getCollisions() {
  147. if (this.collisions == null) this.collisions = new HashMap<String, String>();
  148. return this.collisions;
  149. }
  150. public void clearCollisions() {
  151. this.collisions = null;
  152. }
  153. protected HierarchyNode buildNode(HierarchyNode parent, String nodeid) {
  154. return buildNode(parent, nodeid, null);
  155. }
  156. protected HierarchyNode buildNode(HierarchyNode parent, String nodeid, Page page) {
  157. //does parent have this node already?
  158. if (!parent.getChildren().isEmpty()) {
  159. for (Iterator iter = parent.getChildIterator(); iter.hasNext();) {
  160. HierarchyNode child = (HierarchyNode) iter.next();
  161. if (same(nodeid, child)) {
  162. if (child.getPage() == null && page != null) child.setPage(page);
  163. return child;
  164. }
  165. }
  166. }
  167. //if not add it here
  168. HierarchyNode child = new HierarchyNode();
  169. if (page != null) child.setPage(page);
  170. child.setName(nodeid);
  171. parent.addChild(child);
  172. return child;
  173. }
  174. private boolean same(String nodeid, HierarchyNode child) {
  175. if (child == null) return false;
  176. if (child.getName().equals(nodeid)) return true;
  177. if (child.getPage() == null) return false;
  178. return child.getPage().getFile().getName().endsWith(nodeid+".txt");
  179. }
  180. protected String createNodeId(String id, String type) {
  181. return type+id;
  182. }
  183. private void assignRootPage(HierarchyNode root) {
  184. HierarchyNode home;
  185. if (root.getChildren().isEmpty()) {
  186. home = new HierarchyNode();
  187. home.setChildrenComparator(new SmfTimeComparator());
  188. home.setName(DEFAULT_ROOTPAGENAME);
  189. root.addChild(home);
  190. }
  191. }
  192. private void handleComments(HierarchyNode root) {
  193. if (this.getProperties().containsKey(PROPKEY_COMMENTS) &&
  194. Boolean.parseBoolean(this.getProperties().getProperty(PROPKEY_COMMENTS))) {
  195. Vector<HierarchyNode> topicNodes = getTopicNodes(root);
  196. for (HierarchyNode topic : topicNodes) {
  197. Set<HierarchyNode> children = topic.getChildren();
  198. for (Iterator iter = children.iterator(); iter.hasNext();) {
  199. HierarchyNode reply = (HierarchyNode) iter.next();
  200. String comment = createComment(reply.getName());
  201. if (topic.getPage() == null) continue;// could happen if we're converting only one file
  202. topic.getPage().addComment(comment);
  203. }
  204. }
  205. }
  206. }
  207. Pattern topicPattern = Pattern.compile("_top\\d+\\.txt$");
  208. /**
  209. * recursive method for getting all the topic nodes from within
  210. * a given root. Note that because of child boards, we can't assume
  211. * that topics are all on the 3rd level
  212. * @param root
  213. * @return
  214. */
  215. protected Vector<HierarchyNode> getTopicNodes(HierarchyNode root) {
  216. Vector<HierarchyNode> topics = new Vector<HierarchyNode>();
  217. String filename;
  218. //use the filename to determine the page's level
  219. if (root.getPage() == null ||
  220. root.getPage().getFile() == null ||
  221. root.getPage().getFile().getName() == null) {
  222. filename = "";
  223. }
  224. else filename = root.getPage().getFile().getName();
  225. //if it's a topic, add it
  226. if (topicPattern.matcher(filename).find())
  227. topics.add(root);
  228. else { //check it's children for topic pages
  229. for (HierarchyNode node : root.getChildren()) {
  230. topics.addAll(getTopicNodes(node));
  231. }
  232. }
  233. return topics;
  234. }
  235. protected String createComment(String name) {
  236. return "h1. [" + name + "]\n{include:" + name + "}";
  237. }
  238. /**
  239. * Pattern for identifying object id from filename
  240. */
  241. Pattern id = Pattern.compile("(\\d+)(\\.txt)?$");
  242. /**
  243. * Uses the id of the object to determine it's sort order.
  244. * (Smaller ids mean the object was created earlier.)
  245. */
  246. public class SmfTimeComparator implements Comparator {
  247. public int compare(Object arg0, Object arg1) {
  248. HierarchyNode node0 = (HierarchyNode) arg0;
  249. HierarchyNode node1 = (HierarchyNode) arg1;
  250. String name0 = node0.getName();
  251. String name1 = node1.getName();
  252. if (!id.matcher(name0).find() && node0.getPage() != null)
  253. name0 = node0.getPage().getFile().getName();
  254. if (!id.matcher(name1).find() && node1.getPage() != null)
  255. name1 = node1.getPage().getFile().getName();
  256. Matcher idFinder0 = id.matcher(name0);
  257. Matcher idFinder1 = id.matcher(name1);
  258. if (idFinder0.find() && idFinder1.find()) {
  259. String idStr0 = idFinder0.group(1);
  260. String idStr1 = idFinder1.group(1);
  261. int id0 = Integer.parseInt(idStr0);
  262. int id1 = Integer.parseInt(idStr1);
  263. return id0 - id1;
  264. }
  265. return 0;
  266. }
  267. }
  268. }