1 /* 2 * The contents of this file are subject to the terms of the Common Development and 3 * Distribution License (the License). You may not use this file except in compliance with the 4 * License. 5 * 6 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 7 * specific language governing permission and limitations under the License. 8 * 9 * When distributing Covered Software, include this CDDL Header Notice in each file and include 10 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 11 * Header, with the fields enclosed by brackets [] replaced by your own identifying 12 * information: "Portions copyright [year] [name of copyright owner]". 13 * 14 * Copyright 2014 ForgeRock AS. 15 */ 16 17 package org.forgerock.maven.plugins.xcite; 18 19 import java.util.StringTokenizer; 20 import java.util.regex.Pattern; 21 22 /** 23 * A citation references part of a text file to quote. 24 * 25 * <p> 26 * 27 * The string representation includes the path to the file to cite, 28 * and optionally start and end markers to frame the text to quote. 29 * 30 * <p> 31 * 32 * For example, {@code [/path/to/script.sh:# start:# end]} 33 * 34 * <p> 35 * 36 * Notice that the example has a UNIX-style path, {@code /path/to/script.sh}, 37 * a start marker, {@code # start}, and an end marker, {@code # end}. 38 * The file, {@code script.sh}, could have text to quote between the markers. 39 * For example, {@code script.sh} might have the following content: 40 * 41 * <pre> 42 * #!/bin/bash 43 * 44 * # start 45 * wall <<EOM 46 * Hello world 47 * EOM 48 * # end 49 * 50 * exit 51 * </pre> 52 * 53 * In this case the quote would be the following: 54 * 55 * <pre> 56 * wall <<EOM 57 * Hello world 58 * EOM 59 * </pre> 60 * 61 * Start and end markers depend on the language of the source text. 62 * Markers should make sense to people who mark up the source text, 63 * and so they should be composed of visible characters. 64 * Markers must not include the delimiter character 65 * that is used to separate the path and the markers. 66 * 67 * <p> 68 * 69 * Markers either both be on the same line as the text to quote, 70 * or should be on separate lines from the text to quote. 71 * In other words, you can prepare a text to quote a single line 72 * either by adding a start marker on the line before 73 * and an end marker on the line after, 74 * or by prepending a start marker to the line before the text to quote 75 * and appending an end marker after. 76 * 77 * <p> 78 * 79 * The following example shows markers on separate lines. 80 * 81 * <pre> 82 * #start 83 * This is the text to quote. 84 * #end 85 * </pre> 86 * 87 * <p> 88 * 89 * The following example shows markers on the same line as the text to quote. 90 * 91 * <pre> 92 * #start This is the text to quote. #end 93 * </pre> 94 * 95 * More formally, the citation string representation is as follows. 96 * 97 * <pre> 98 * 99 * citation = "[" path delimiter start-marker delimiter end-marker "]" 100 * citation / "[" path delimiter start-marker "]" 101 * citation / "[" path "]" 102 * 103 * path = File.getPath() ; Depends on the OS, 104 * ; does not include delimiter 105 * 106 * delimiter = ":" / "%" ; Default: ":" 107 * 108 * start-marker = 1*(VCHAR excluding delimiter) ; Depends on source language 109 * 110 * end-marker = 1*(VCHAR excluding delimiter) ; Depends on source language 111 * 112 * </pre> 113 * 114 * Most systems allow file names that can cause problems with this scheme. 115 * Working around the problem is an exercise for the reader. 116 */ 117 public class Citation { 118 119 /** 120 * Initial wrapper to open the citation string representation. 121 */ 122 private static final String OPEN = "["; 123 124 /** 125 * Final wrapper to close the citation string representation. 126 */ 127 private static final String CLOSE = "]"; 128 129 private String path; // Pathname for the file to quote 130 private char delimiter; // Delimiter for path, markers 131 private String start; // Start marker 132 private String end; // End marker 133 134 /** 135 * Construct a citation from a path alone. 136 * 137 * <p> 138 * 139 * The path cannot be null. 140 * 141 * @param path The pathname string for the file to quote. Not null. 142 * @throws IllegalArgumentException Path is broken somehow. 143 */ 144 public Citation(final String path) { 145 build(path, ':', null, null); 146 } 147 148 /** 149 * Construct a citation from a path, delimiter, and start marker, 150 * when the start marker and the end marker are the same. 151 * 152 * <p> 153 * 154 * The path cannot be null. 155 * 156 * <p> 157 * 158 * The start marker cannot contain the delimiter. 159 * 160 * @param path The pathname string for the file to quote. Not null. 161 * @param delimiter The delimiter for path, markers. 162 * @param start The start marker. If null or "", only path is useful. 163 * @throws IllegalArgumentException Path or marker is broken somehow. 164 */ 165 public Citation(final String path, final char delimiter, final String start) { 166 build(path, delimiter, start, null); 167 } 168 169 /** 170 * Construct a citation from a path, delimiter, start marker, and end marker. 171 * 172 * <p> 173 * 174 * The path cannot be null. 175 * 176 * <p> 177 * 178 * The markers cannot contain the delimiter. 179 * 180 * @param path The pathname string for the file to quote. Not null. 181 * @param delimiter The delimiter for path, markers. 182 * @param start The start marker. If null or "", only path is useful. 183 * @param end The end marker. If null or "", {@code start} is both. 184 * @throws IllegalArgumentException Path or marker is broken somehow. 185 */ 186 public Citation(final String path, final char delimiter, 187 final String start, final String end) { 188 build(path, delimiter, start, end); 189 } 190 191 /** 192 * Builder for constructing an instance. 193 * 194 * @param path The pathname string for the file to quote. Not null. 195 * @param delimiter The delimiter for path, markers. 196 * @param start The start marker. If null or "", only path is useful. 197 * @param end The end marker. If null or "", {@code start} is both. 198 * @throws IllegalArgumentException Path or marker is broken somehow. 199 */ 200 private void build(final String path, final char delimiter, 201 final String start, final String end) { 202 setPath(path); 203 setDelimiter(delimiter); 204 setStart(start); 205 setEnd(end); 206 } 207 208 /** 209 * Returns the value of the path. 210 * 211 * @return The value of the path. 212 */ 213 public String getPath() { 214 return this.path; 215 } 216 217 /** 218 * Sets the pathname string for the file to quote. 219 * 220 * @param path The pathname string for the file to quote. 221 * @throws IllegalArgumentException Path cannot be null. 222 */ 223 public void setPath(final String path) { 224 if (path == null) { 225 throw new IllegalArgumentException("Path cannot be null."); 226 } else { 227 this.path = path; 228 } 229 } 230 231 /** 232 * Returns the value of the delimiter. 233 * 234 * @return The value of the delimiter. 235 */ 236 public char getDelimiter() { 237 return this.delimiter; 238 } 239 240 /** 241 * Sets the value of the delimiter. 242 * 243 * @param delimiter The delimiter for path, markers. 244 */ 245 public void setDelimiter(char delimiter) { 246 if (delimiter == ':' || delimiter == '%') { 247 this.delimiter = delimiter; 248 } else { 249 this.delimiter = ':'; 250 } 251 } 252 253 /** 254 * Returns the value of the start marker, which can be empty. 255 * 256 * @return The value of the start marker, which can be empty. 257 */ 258 public String getStart() { 259 return this.start; 260 } 261 262 /** 263 * Sets the value of the start marker, which must not contain the delimiter. 264 * 265 * @param start The value of the start marker. 266 * @throws IllegalArgumentException Start marker contains the delimiter. 267 */ 268 public void setStart(String start) { 269 if (isNullOrEmpty(start)) { 270 this.start = ""; 271 } else if (!start.contains(Character.toString(getDelimiter()))) { 272 this.start = start; 273 } else { 274 throw new IllegalArgumentException("Start marker: " + start 275 + " contains delimiter: " + getDelimiter()); 276 } 277 } 278 279 /** 280 * Returns the value of the start marker, which can be empty. 281 * 282 * @return The value of the start marker, which can be empty. 283 */ 284 public String getEnd() { 285 return this.end; 286 } 287 288 /** 289 * Sets the value of the end marker, which must not contain the delimiter. 290 * 291 * @param end The value of the end marker. 292 * @throws IllegalArgumentException End marker contains the delimiter. 293 */ 294 public void setEnd(String end) { 295 if (isNullOrEmpty(end)) { 296 this.end = getStart(); 297 } else if (!end.contains(Character.toString(getDelimiter()))) { 298 this.end = end; 299 } else { 300 throw new IllegalArgumentException("End marker: " + end 301 + " contains delimiter: " + getDelimiter()); 302 } 303 } 304 305 /** 306 * Returns true if the string is null or the string is empty. 307 * 308 * @param string The string. 309 * @return Whether the string is null or empty. 310 */ 311 private static boolean isNullOrEmpty(final String string) { 312 return (string == null || string.isEmpty()); 313 } 314 315 /** 316 * Returns the string representation of this citation. 317 * 318 * @return The string representation of this citation. 319 */ 320 public String toString() { 321 String start = 322 (isNullOrEmpty(getStart()) ? "" : getDelimiter() + getStart()); 323 324 String end; 325 if (isNullOrEmpty(getStart()) || isNullOrEmpty(getEnd())) { 326 end = ""; 327 } else { 328 end = getDelimiter() + getEnd(); 329 } 330 331 return OPEN + getPath() + start + end + CLOSE; 332 } 333 334 /** 335 * Returns a Citation from the string representation. 336 * 337 * @param citation The string representation. 338 * @param delimiter One of ":" or "%". 339 * @return A Citation corresponding to the string representation, 340 * or null if the string representation does not parse. 341 */ 342 public static Citation valueOf(final String citation, final String delimiter) { 343 344 // No null delimiters. 345 if (delimiter == null) { 346 return null; 347 } 348 349 // No illegal delimiters. 350 if (!(delimiter.equals(":") || delimiter.equals("%"))) { 351 return null; 352 } 353 354 // OPEN marks the start of the citation string. 355 if (!citation.startsWith(OPEN)) { 356 return null; 357 } 358 359 // CLOSE marks the end of the citation string. 360 if (!citation.endsWith(CLOSE)) { 361 return null; 362 } 363 364 // Remove the OPEN & CLOSE wrappers. 365 String unwrapped = citation 366 .replaceFirst(Pattern.quote(OPEN), "") 367 .replaceFirst(Pattern.quote(CLOSE), ""); 368 369 // Starting with a delimiter means path is null. 370 if (unwrapped.startsWith(delimiter)) { 371 return null; 372 } 373 374 // Delimiters should delimit actual values, not empty strings. 375 if (unwrapped.contains(delimiter + delimiter)) { 376 return null; 377 } 378 379 // Tokenize using the delimiter, as values do not contain the delimiter. 380 StringTokenizer st = new StringTokenizer(unwrapped, delimiter); 381 String path = null; 382 String start = null; 383 String end = null; 384 385 if (st.hasMoreTokens()) { // path 386 path = st.nextToken(); 387 } 388 if (st.hasMoreTokens()) { // start 389 start = st.nextToken(); 390 } 391 if (st.hasMoreTokens()) { // end 392 end = st.nextToken(); 393 } 394 if (st.hasMoreTokens()) { 395 return null; 396 } 397 398 // Path must not be null. 399 if (isNullOrEmpty(path)) { 400 return null; 401 } 402 403 return new Citation(path, ':', start, end); 404 } 405 406 /** 407 * Returns a Citation from the string representation, 408 * assuming the default delimiter, {@code :}. 409 * 410 * @param citation The string representation. 411 * @return A Citation corresponding to the string representation, 412 * or null if the string representation does not parse. 413 */ 414 public static Citation valueOf(final String citation) { 415 return valueOf(citation, ":"); 416 } 417 }