PageRenderTime 47ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://bitbucket.org/atlassianlabs/universal-wiki-connector
Java | 361 lines | 262 code | 36 blank | 63 comment | 49 complexity | 4f2dfb915e53c0b2a57394d5141c2a70 MD5 | raw file
  1. package com.atlassian.uwc.hierarchies;
  2. import java.util.Collection;
  3. import java.util.Comparator;
  4. import java.util.Iterator;
  5. import java.util.Properties;
  6. import java.util.TreeMap;
  7. import java.util.Vector;
  8. import java.util.regex.Matcher;
  9. import java.util.regex.Pattern;
  10. import org.apache.log4j.Logger;
  11. import sun.tools.tree.AddExpression;
  12. import com.atlassian.uwc.converters.tikiwiki.RegexUtil;
  13. import com.atlassian.uwc.ui.Page;
  14. public class ContentHierarchy implements HierarchyBuilder {
  15. Properties props = new Properties();
  16. Logger log = Logger.getLogger(this.getClass());
  17. /* Constants */
  18. public static final String PROP_PATTERN = "content-hierarchy-pattern";
  19. public static final String PROP_CURRENT = "content-hierarchy-pattern-includes-current";
  20. public static final String PROP_DELIM = "content-hierarchy-delim";
  21. public static final String PROP_ROOT = "content-hierarchy-default-root";
  22. public static final String PROP_SETNAME = "content-hierarchy-setname";
  23. private static final String PROP_HISTORY = "switch.page-history-preservation";
  24. private static final String PROP_HISTORY_SUFFIX = "suffix.page-history-preservation";
  25. public static final String DEFAULT_PATTERN = "\\{orig-title:([^}]*)\\}";
  26. public static final String DEFAULT_CURRENT = "true";
  27. public static final String DEFAULT_DELIM = "/";
  28. public static final String DEFAULT_ROOT = "";
  29. private static final String DEFAULT_SETNAME="false";
  30. private static final String DEFAULT_HISTORY_SUFFIX = "-#";
  31. private static final String DEFAULT_HISTORY = "false";
  32. public HierarchyNode buildHierarchy(Collection<Page> pages) {
  33. log.debug("Checking Pages object is valid.");
  34. //check that pages is valid and non-empty
  35. if (pages == null) {
  36. String message = "--> Cannot build hierarchy. Pages object is null.";
  37. log.debug(message);
  38. return null;
  39. }
  40. if (pages.isEmpty()) {
  41. String message = "--> Cannot build hierarchy. Pages object is empty.";
  42. log.debug(message);
  43. return null;
  44. }
  45. log.debug("--> Pages object is valid.");
  46. //set up root node
  47. HierarchyNode root = new HierarchyNode();
  48. root.setChildrenComparator(getVersionComparator());
  49. HierarchyNode pen = getPenultimateNode(getRootName(), root);
  50. log.info("Building Hierarchy.");
  51. boolean hasCurrent = getHasCurrent();
  52. log.debug("Creating Nodes...");
  53. //create a node for each page; ancestorstring -> node
  54. TreeMap<String, HierarchyNode> nodes = getAncestorNodeMap(pages, hasCurrent);
  55. //connect leaf to each parent
  56. log.debug("Building Connections...");
  57. for (Iterator iter = nodes.keySet().iterator(); iter.hasNext();) {
  58. //get node
  59. String hierarchy = (String) iter.next();
  60. HierarchyNode node = nodes.get(hierarchy);
  61. //get ancestors
  62. Vector<String> ancestors = getAncestors(hierarchy);
  63. setNodeName(node, ancestors);
  64. log.debug(".. connecting node: " + node.getName());
  65. setTopLevelNodes(pen, node, ancestors);
  66. //go through each ancestor, find its node and connect them together
  67. HierarchyNode currentChild = node;
  68. for (int i = 0; i < ancestors.size(); i++) {
  69. String fullAncestor = ancestors.get(i);
  70. HierarchyNode ancestorNode = nodes.get(fullAncestor);
  71. if (ancestorNode == currentChild) continue;
  72. if (ancestorNode != null)
  73. log.debug(".... to node: " + ancestorNode.getName());
  74. else {
  75. log.error(".... unexpected name: " + fullAncestor + " - SKIPPING");
  76. continue;
  77. }
  78. //the addChild method handles redundant children for us
  79. ancestorNode.addChild(currentChild);
  80. log.debug(".... has " + ancestorNode.getChildren().size() + " children");
  81. currentChild = ancestorNode;
  82. }
  83. }
  84. return root;
  85. }
  86. /**
  87. * sets the node name. If the SETNAME property is false (default), preserves the existing name from
  88. * the page object. If SETNAME property is true, uses the last String in the ancestors list
  89. * to set the node name.
  90. * @param node
  91. * @param ancestors
  92. */
  93. private void setNodeName(HierarchyNode node, Vector<String> ancestors) {
  94. if (shouldSetName() && !preservingHistory()) { //name = last node of first element
  95. String[] nodes = ancestors.firstElement().split(getDelim());
  96. int index = nodes.length-1; //example: Plants/Trees
  97. String leafname = nodes[index];
  98. node.setName(leafname);
  99. node.getPage().setName(leafname);
  100. }
  101. else if (shouldSetName() && preservingHistory()) { //name = penultimate node
  102. String[] nodes = ancestors.firstElement().split(getDelim());
  103. int index = nodes.length>1?nodes.length-2:0; //example: Plants/Trees/1
  104. String leafname = nodes[index];
  105. node.setName(leafname);
  106. node.getPage().setName(leafname);
  107. }
  108. else { //preserve existing name from Page object
  109. node.setName(node.getPage().getName());
  110. }
  111. }
  112. private void setTopLevelNodes(HierarchyNode pen, HierarchyNode node, Vector<String> ancestors) {
  113. boolean isTopLevel = ancestors.size() == 1;
  114. if (isTopLevel) pen.addChild(node);
  115. }
  116. /**
  117. * creates a map of ancestor keys -> node
  118. * An ancestor key must be unique, so it represents the entire hierarchy/ancestor
  119. * path until the node. If we're preserving history, we must maintain uniqueness by appending
  120. * the page version to the ancestor key.
  121. * @param pages
  122. * @param hasCurrent
  123. * @return
  124. */
  125. private TreeMap<String, HierarchyNode> getAncestorNodeMap(Collection<Page> pages, boolean hasCurrent) {
  126. TreeMap<String,HierarchyNode> nodes = new TreeMap<String, HierarchyNode>();
  127. for (Page page : pages) {
  128. log.debug(".. creating node: " + page.getName() + " version: "+ page.getVersion());
  129. if (page == null) continue;
  130. HierarchyNode node = new HierarchyNode();
  131. node.setPage(page);
  132. node.setChildrenComparator(getVersionComparator());
  133. String hierarchy = getHierarchy(page);
  134. //if we don't have the current node's name in the ancestor path, add it
  135. if (!hasCurrent) {
  136. if (!"".equals(hierarchy)) hierarchy += getDelim();
  137. hierarchy += page.getName();
  138. }
  139. //if we're preserving history, add version to preserve path uniqueness
  140. if (preservingHistory()) {
  141. String filename = page.getFile().getName();
  142. String suffix = getHistorySuffix();
  143. String version = getPageVersion(filename, suffix);
  144. if (!"".equals(hierarchy)) hierarchy += getDelim();
  145. hierarchy += version;
  146. }
  147. nodes.put(hierarchy, node);
  148. }
  149. return nodes;
  150. }
  151. private HierarchyNode getPenultimateNode(String rootname, HierarchyNode root) {
  152. HierarchyNode pen;
  153. if (rootname != null && !"".equals(rootname)) {
  154. log.debug("Page root set to: " + rootname);
  155. pen = new HierarchyNode();
  156. pen.setName(rootname);
  157. root.addChild(pen);
  158. }
  159. else pen = root;
  160. pen.setChildrenComparator(getVersionComparator());
  161. return pen;
  162. }
  163. private static VersionComparator versionComparator;
  164. private Comparator getVersionComparator() {
  165. if (versionComparator == null) versionComparator = new VersionComparator();
  166. return versionComparator;
  167. }
  168. /**
  169. * @return the root name for the hierarchy. Can be set with property
  170. * "content-hierarchy-default-root". If blank top level pages will be given no parent,
  171. * ie. will be an orphan page, and sibling to Home
  172. */
  173. protected String getRootName() {//provided by the misc props framework
  174. return getProperties().getProperty(PROP_ROOT, DEFAULT_ROOT);
  175. }
  176. private boolean getHasCurrent() {//provided by the misc props framework
  177. return Boolean.parseBoolean(getProperties().getProperty(PROP_CURRENT, DEFAULT_CURRENT));
  178. }
  179. private String getDelim() { //provided by the misc props framework
  180. return getProperties().getProperty(PROP_DELIM, DEFAULT_DELIM);
  181. }
  182. /**
  183. * return true if the content hierarchy should set the name of the page based on a hierarchy leaf
  184. */
  185. private boolean shouldSetName() {//provided by the misc props framework
  186. return Boolean.parseBoolean(getProperties().getProperty(PROP_SETNAME, DEFAULT_SETNAME));
  187. }
  188. private boolean preservingHistory() { //provided by the ConverterEngine/page histories framework
  189. return Boolean.parseBoolean(getProperties().getProperty(PROP_HISTORY, DEFAULT_HISTORY));
  190. }
  191. private String getHistorySuffix() { //provided by the ConverterEngine/page histories framework
  192. return getProperties().getProperty(PROP_HISTORY_SUFFIX, DEFAULT_HISTORY_SUFFIX);
  193. }
  194. /**
  195. * find the hierarchy from inside the page content, using the
  196. * property "content-hierarchy-pattern", a regex whose group 1 should be the ancestor string
  197. * @param page
  198. * @return
  199. */
  200. protected String getHierarchy(Page page) {
  201. String patternString = getProperties().getProperty(PROP_PATTERN, DEFAULT_PATTERN);
  202. Pattern pattern;
  203. Matcher finder;
  204. try {
  205. pattern = Pattern.compile(patternString);
  206. finder = pattern.matcher(page.getUnchangedSource());
  207. if (finder.find()) {
  208. try {
  209. return finder.group(1); //the pattern must have a group 1
  210. } catch (IndexOutOfBoundsException e) {
  211. String msg = "BAD_PROPERTY: Regular Expression '" + patternString + "' could not be used. " +
  212. "Make sure it has at least one group. Cause: " + e.getMessage();
  213. log.error(msg);
  214. throw new IllegalArgumentException(msg, e);
  215. }
  216. }
  217. } catch (Exception e) {
  218. String msg = "BAD_PROPERTY: Regular Expression '" + patternString + "' could not be compiled. " +
  219. "Cause: " + e.getMessage();
  220. log.error(msg);
  221. throw new IllegalArgumentException(msg, e);
  222. }
  223. return null;
  224. }
  225. /**
  226. * Split given hierarchy string into ancestor strings, such that
  227. * each ancestor string contains it's entire ancestor set as well as itself.
  228. * The delimiter can be set using the property: "content-hierarchy-delim".
  229. * @param hierarchy
  230. * @return set of ancestor strings
  231. * Example. Hierarchy: A/B/C <br/>
  232. * would return A/B/C, A/B, A
  233. */
  234. protected Vector<String> getAncestors(String hierarchy) {
  235. String delim = getProperties().getProperty(PROP_DELIM, DEFAULT_DELIM);
  236. Vector<String> ancestors = new Vector<String>();
  237. //spilt hierarchy using delim
  238. Pattern delimPattern = Pattern.compile("" +
  239. "[^" +
  240. "\\Q"+delim+"\\E" +
  241. "]*");
  242. Matcher delimFinder = delimPattern.matcher(hierarchy);
  243. while (delimFinder.find()) {
  244. String ancestor = delimFinder.group().trim();
  245. if (ancestor == null || "".equals(ancestor)) continue;
  246. ancestors.add(ancestor);
  247. }
  248. //create unique ancestor keys.
  249. String current = "";
  250. int max = ancestors.size();
  251. for (int i = 0; i < max; i++) {
  252. String ancestor = (String) ancestors.get(i);
  253. String full = ancestor;
  254. //build this ancestor's key using its ancestors
  255. if (!"".equals(current)) {
  256. current += delim;
  257. full = current + ancestor;
  258. ancestors.set(i, full);
  259. }
  260. //if preserving history, we must maintain uniqueness
  261. //by using version numbers. When version is unknown, 1 is sufficient
  262. if (preservingHistory() && !full.matches(".*\\/\\d+$")) {
  263. full += delim + "1";
  264. ancestors.set(i, full);
  265. }
  266. //remember this ancestor key for next time
  267. current += ancestor;
  268. }
  269. //We need the ancestor keys in descending order
  270. Vector<String> descending = new Vector<String>();
  271. current = "";
  272. for (int i = max-1; i >= 0; i--) {
  273. String ancestor = (String) ancestors.get(i);
  274. //double check we didn't end up with duplicates due to preserving history
  275. if (ancestor.equals(current)) continue;
  276. if (ancestor.replaceFirst("\\d+$", "").equals(current.replaceFirst("\\d+$", "")))
  277. continue;
  278. current = ancestor;
  279. descending.add(ancestor);
  280. }
  281. return descending;
  282. }
  283. Pattern hashPattern = Pattern.compile("#+");
  284. /**
  285. * @param filename
  286. * @param suffix
  287. * @return the version represented in the filename, using suffix as a pattern
  288. */
  289. protected String getPageVersion(String filename, String suffix) {
  290. Matcher hashFinder = hashPattern.matcher(suffix);
  291. String suffixReplaceRegex = "";
  292. if (hashFinder.find()) {
  293. suffixReplaceRegex = hashFinder.replaceAll("(\\\\d+)");
  294. suffixReplaceRegex = "(.*)" + suffixReplaceRegex;
  295. }
  296. Pattern suffixReplacePattern = Pattern.compile(suffixReplaceRegex);
  297. Matcher suffixReplacer = suffixReplacePattern.matcher(filename);
  298. if (suffixReplacer.find()) {
  299. return suffixReplacer.group(2);
  300. }
  301. return null;
  302. }
  303. public Properties getProperties() {
  304. if (props == null)
  305. props = new Properties();
  306. return props;
  307. }
  308. public void setProperties(Properties properties) {
  309. this.props = properties;
  310. }
  311. public class VersionComparator implements Comparator {
  312. public int compare(Object o1, Object o2) {
  313. HierarchyNode n1 = (HierarchyNode) o1;
  314. HierarchyNode n2 = (HierarchyNode) o2;
  315. String name1 = n1.getName();
  316. String name2 = n2.getName();
  317. log.debug("comparing: " + name1 + " and " + name2);
  318. int nameCompare = name1.compareTo(name2);
  319. if (nameCompare != 0) return nameCompare;
  320. int v1 = n1.getPage().getVersion();
  321. int v2 = n2.getPage().getVersion();
  322. return v1 - v2;
  323. }
  324. }
  325. }