PageRenderTime 56ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/java/com/onarandombox/multiverseinventories/util/CommentedYamlConfiguration.java

https://gitlab.com/kidaa/Multiverse-Inventories
Java | 312 lines | 210 code | 17 blank | 85 comment | 53 complexity | 337c2ee49e3530e896fa4a62bc5ad791 MD5 | raw file
  1. package com.onarandombox.multiverseinventories.util;
  2. import com.feildmaster.lib.configuration.EnhancedConfiguration;
  3. import org.bukkit.configuration.file.FileConfiguration;
  4. import java.io.BufferedReader;
  5. import java.io.File;
  6. import java.io.FileInputStream;
  7. import java.io.FileOutputStream;
  8. import java.io.IOException;
  9. import java.io.InputStream;
  10. import java.io.InputStreamReader;
  11. import java.io.OutputStreamWriter;
  12. import java.io.Reader;
  13. import java.io.StringWriter;
  14. import java.io.UnsupportedEncodingException;
  15. import java.io.Writer;
  16. import java.util.HashMap;
  17. import java.util.List;
  18. /**
  19. * A Configuration wrapper class that allows for comments to be applied to the config paths.
  20. */
  21. public final class CommentedYamlConfiguration {
  22. private HashMap<String, String> comments;
  23. private File file;
  24. private FileConfiguration config = null;
  25. private boolean doComments;
  26. public CommentedYamlConfiguration(File file, boolean doComments) {
  27. super();
  28. comments = new HashMap<String, String>();
  29. this.file = file;
  30. this.doComments = doComments;
  31. }
  32. /**
  33. * Loads this Configuration object into memory.
  34. */
  35. public void load() throws UnsupportedEncodingException {
  36. config = new EnhancedConfiguration(file);
  37. }
  38. /**
  39. * @return The underlying configuration object.
  40. */
  41. public FileConfiguration getConfig() {
  42. return this.config;
  43. }
  44. /**
  45. * Saves the file as per normal for YamlConfiguration and then parses the file and inserts
  46. * comments where necessary.
  47. *
  48. * @return True if succesful.
  49. */
  50. public boolean save() {
  51. boolean saved = true;
  52. // Save the config just like normal
  53. try {
  54. config.save(file);
  55. } catch (Exception e) {
  56. saved = false;
  57. }
  58. if (!doComments) {
  59. return saved;
  60. }
  61. // if there's comments to add and it saved fine, we need to add comments
  62. if (!comments.isEmpty() && saved) {
  63. // String array of each line in the config file
  64. String[] yamlContents =
  65. this.convertFileToString(file).split("[" + System.getProperty("line.separator") + "]");
  66. // This will hold the entire newly formatted config
  67. StringBuilder newContents = new StringBuilder();
  68. String initialContents = config.options().header();
  69. if (initialContents == null) {
  70. initialContents = "";
  71. }
  72. newContents.append(initialContents)
  73. .append(System.getProperty("line.separator"))
  74. .append(System.getProperty("line.separator"));
  75. // This holds the current path the lines are at in the config
  76. StringBuilder currentPath = new StringBuilder();
  77. // This tells if the specified path has already been commented
  78. boolean commentedPath = false;
  79. // This flags if the line is a node or unknown text.
  80. boolean node = false;
  81. // The depth of the path. (number of words separated by periods - 1)
  82. int depth = 0;
  83. // TODO find a better solution here?
  84. // This will cause the first line to be ignored.
  85. boolean firstLine = true;
  86. // Loop through the config lines
  87. for (final String line : yamlContents) {
  88. if (firstLine) {
  89. firstLine = false;
  90. if (line.startsWith("#")) {
  91. continue;
  92. }
  93. }
  94. // If the line is a node (and not something like a list value)
  95. if (line.contains(": ") || (line.length() > 1 && line.charAt(line.length() - 1) == ':')) {
  96. // This is a new node so we need to mark it for commenting (if there are comments)
  97. commentedPath = false;
  98. // This is a node so flag it as one
  99. node = true;
  100. // Grab the index of the end of the node name
  101. int index = 0;
  102. index = line.indexOf(": ");
  103. if (index < 0) {
  104. index = line.length() - 1;
  105. }
  106. // If currentPath is empty, store the node name as the currentPath. (this is only on the first iteration, i think)
  107. if (currentPath.toString().isEmpty()) {
  108. currentPath = new StringBuilder(line.substring(0, index));
  109. } else {
  110. // Calculate the whitespace preceding the node name
  111. int whiteSpace = 0;
  112. for (int n = 0; n < line.length(); n++) {
  113. if (line.charAt(n) == ' ') {
  114. whiteSpace++;
  115. } else {
  116. break;
  117. }
  118. }
  119. // Find out if the current depth (whitespace * 2) is greater/lesser/equal to the previous depth
  120. if (whiteSpace / 2 > depth) {
  121. // Path is deeper. Add a . and the node name
  122. currentPath.append(".").append(line.substring(whiteSpace, index));
  123. depth++;
  124. } else if (whiteSpace / 2 < depth) {
  125. // Path is shallower, calculate current depth from whitespace (whitespace / 2) and subtract that many levels from the currentPath
  126. int newDepth = whiteSpace / 2;
  127. for (int i = 0; i < depth - newDepth; i++) {
  128. currentPath.replace(currentPath.lastIndexOf("."), currentPath.length(), "");
  129. }
  130. // Grab the index of the final period
  131. int lastIndex = currentPath.lastIndexOf(".");
  132. if (lastIndex < 0) {
  133. // if there isn't a final period, set the current path to nothing because we're at root
  134. currentPath = new StringBuilder();
  135. } else {
  136. // If there is a final period, replace everything after it with nothing
  137. currentPath.replace(currentPath.lastIndexOf("."), currentPath.length(), "").append(".");
  138. }
  139. // Add the new node name to the path
  140. currentPath.append(line.substring(whiteSpace, index));
  141. // Reset the depth
  142. depth = newDepth;
  143. } else {
  144. // Path is same depth, replace the last path node name to the current node name
  145. int lastIndex = currentPath.lastIndexOf(".");
  146. if (lastIndex < 0) {
  147. // if there isn't a final period, set the current path to nothing because we're at root
  148. currentPath = new StringBuilder();
  149. } else {
  150. // If there is a final period, replace everything after it with nothing
  151. currentPath.replace(currentPath.lastIndexOf("."), currentPath.length(), "").append(".");
  152. }
  153. //currentPath = currentPath.replace(currentPath.substring(currentPath.lastIndexOf(".")), "");
  154. currentPath.append(line.substring(whiteSpace, index));
  155. }
  156. }
  157. } else {
  158. node = false;
  159. }
  160. StringBuilder newLine = new StringBuilder(line);
  161. if (node) {
  162. String comment = null;
  163. if (!commentedPath) {
  164. // If there's a comment for the current path, retrieve it and flag that path as already commented
  165. comment = comments.get(currentPath.toString());
  166. }
  167. if (comment != null && !comment.isEmpty()) {
  168. // Add the comment to the beginning of the current line
  169. newLine.insert(0, System.getProperty("line.separator")).insert(0, comment);
  170. comment = null;
  171. commentedPath = true;
  172. }
  173. /* Old code for removing uncommented lines.
  174. * May need reworking.
  175. if (comment != null || (line.length() > 1 && line.charAt(line.length() - 1) == ':')) {
  176. // Add the (modified) line to the total config String
  177. // This modified version will not write the config if a comment is not present
  178. newContents += line + System.getProperty("line.separator");
  179. }
  180. */
  181. }
  182. newLine.append(System.getProperty("line.separator"));
  183. // Add the (modified) line to the total config String
  184. newContents.append(newLine.toString());
  185. }
  186. /*
  187. * Due to a bukkit bug we need to strip any extra new lines from the
  188. * beginning of this file, else they will multiply.
  189. */
  190. /*
  191. while (newContents.startsWith(System.getProperty("line.separator"))) {
  192. newContents = newContents.replaceFirst(System.getProperty("line.separator"), "");
  193. }
  194. */
  195. try {
  196. // Write the string to the config file
  197. this.stringToFile(newContents.toString(), file);
  198. } catch (IOException e) {
  199. saved = false;
  200. }
  201. }
  202. return saved;
  203. }
  204. /**
  205. * Adds a comment just before the specified path. The comment can be multiple lines. An empty string will indicate a blank line.
  206. *
  207. * @param path Configuration path to add comment.
  208. * @param commentLines Comments to add. One String per line.
  209. */
  210. public void addComment(String path, List<String> commentLines) {
  211. StringBuilder commentstring = new StringBuilder();
  212. String leadingSpaces = "";
  213. for (int n = 0; n < path.length(); n++) {
  214. if (path.charAt(n) == '.') {
  215. leadingSpaces += " ";
  216. }
  217. }
  218. for (String line : commentLines) {
  219. if (!line.isEmpty()) {
  220. line = leadingSpaces + line;
  221. } else {
  222. line = " ";
  223. }
  224. if (commentstring.length() > 0) {
  225. commentstring.append("\r\n");
  226. }
  227. commentstring.append(line);
  228. }
  229. comments.put(path, commentstring.toString());
  230. }
  231. /**
  232. * Pass a file and it will return it's contents as a string.
  233. *
  234. * @param file File to read.
  235. * @return Contents of file. String will be empty in case of any errors.
  236. */
  237. private String convertFileToString(File file) {
  238. final int bufferSize = 1024;
  239. if (file != null && file.exists() && file.canRead() && !file.isDirectory()) {
  240. Writer writer = new StringWriter();
  241. InputStream is = null;
  242. char[] buffer = new char[bufferSize];
  243. try {
  244. is = new FileInputStream(file);
  245. Reader reader = new BufferedReader(
  246. new InputStreamReader(is, "UTF-8"));
  247. int n;
  248. while ((n = reader.read(buffer)) != -1) {
  249. writer.write(buffer, 0, n);
  250. }
  251. } catch (IOException e) {
  252. e.printStackTrace();
  253. } finally {
  254. if (is != null) {
  255. try {
  256. is.close();
  257. } catch (IOException ignore) {
  258. }
  259. }
  260. }
  261. return writer.toString();
  262. } else {
  263. return "";
  264. }
  265. }
  266. /**
  267. * Writes the contents of a string to a file.
  268. *
  269. * @param source String to write.
  270. * @param file File to write to.
  271. * @return True on success.
  272. * @throws java.io.IOException
  273. */
  274. private boolean stringToFile(String source, File file) throws IOException {
  275. OutputStreamWriter out = null;
  276. try {
  277. out = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
  278. source.replaceAll("\n", System.getProperty("line.separator"));
  279. out.write(source);
  280. out.close();
  281. return true;
  282. } catch (IOException e) {
  283. e.printStackTrace();
  284. return false;
  285. } finally {
  286. if (out != null) {
  287. try {
  288. out.close();
  289. } catch (IOException e) {
  290. e.printStackTrace();
  291. }
  292. }
  293. }
  294. }
  295. }