Citation.java
/*
* The contents of this file are subject to the terms of the Common Development and
* Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2014 ForgeRock AS.
*/
package org.forgerock.maven.plugins.xcite;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
/**
* A citation references part of a text file to quote.
*
* <p>
*
* The string representation includes the path to the file to cite,
* and optionally start and end markers to frame the text to quote.
*
* <p>
*
* For example, {@code [/path/to/script.sh:# start:# end]}
*
* <p>
*
* Notice that the example has a UNIX-style path, {@code /path/to/script.sh},
* a start marker, {@code # start}, and an end marker, {@code # end}.
* The file, {@code script.sh}, could have text to quote between the markers.
* For example, {@code script.sh} might have the following content:
*
* <pre>
* #!/bin/bash
*
* # start
* wall <<EOM
* Hello world
* EOM
* # end
*
* exit
* </pre>
*
* In this case the quote would be the following:
*
* <pre>
* wall <<EOM
* Hello world
* EOM
* </pre>
*
* Start and end markers depend on the language of the source text.
* Markers should make sense to people who mark up the source text,
* and so they should be composed of visible characters.
* Markers must not include the delimiter character
* that is used to separate the path and the markers.
*
* <p>
*
* Markers either both be on the same line as the text to quote,
* or should be on separate lines from the text to quote.
* In other words, you can prepare a text to quote a single line
* either by adding a start marker on the line before
* and an end marker on the line after,
* or by prepending a start marker to the line before the text to quote
* and appending an end marker after.
*
* <p>
*
* The following example shows markers on separate lines.
*
* <pre>
* #start
* This is the text to quote.
* #end
* </pre>
*
* <p>
*
* The following example shows markers on the same line as the text to quote.
*
* <pre>
* #start This is the text to quote. #end
* </pre>
*
* More formally, the citation string representation is as follows.
*
* <pre>
*
* citation = "[" path delimiter start-marker delimiter end-marker "]"
* citation / "[" path delimiter start-marker "]"
* citation / "[" path "]"
*
* path = File.getPath() ; Depends on the OS,
* ; does not include delimiter
*
* delimiter = ":" / "%" ; Default: ":"
*
* start-marker = 1*(VCHAR excluding delimiter) ; Depends on source language
*
* end-marker = 1*(VCHAR excluding delimiter) ; Depends on source language
*
* </pre>
*
* Most systems allow file names that can cause problems with this scheme.
* Working around the problem is an exercise for the reader.
*/
public class Citation {
/**
* Initial wrapper to open the citation string representation.
*/
private static final String OPEN = "[";
/**
* Final wrapper to close the citation string representation.
*/
private static final String CLOSE = "]";
private String path; // Pathname for the file to quote
private char delimiter; // Delimiter for path, markers
private String start; // Start marker
private String end; // End marker
/**
* Construct a citation from a path alone.
*
* <p>
*
* The path cannot be null.
*
* @param path The pathname string for the file to quote. Not null.
* @throws IllegalArgumentException Path is broken somehow.
*/
public Citation(final String path) {
build(path, ':', null, null);
}
/**
* Construct a citation from a path, delimiter, and start marker,
* when the start marker and the end marker are the same.
*
* <p>
*
* The path cannot be null.
*
* <p>
*
* The start marker cannot contain the delimiter.
*
* @param path The pathname string for the file to quote. Not null.
* @param delimiter The delimiter for path, markers.
* @param start The start marker. If null or "", only path is useful.
* @throws IllegalArgumentException Path or marker is broken somehow.
*/
public Citation(final String path, final char delimiter, final String start) {
build(path, delimiter, start, null);
}
/**
* Construct a citation from a path, delimiter, start marker, and end marker.
*
* <p>
*
* The path cannot be null.
*
* <p>
*
* The markers cannot contain the delimiter.
*
* @param path The pathname string for the file to quote. Not null.
* @param delimiter The delimiter for path, markers.
* @param start The start marker. If null or "", only path is useful.
* @param end The end marker. If null or "", {@code start} is both.
* @throws IllegalArgumentException Path or marker is broken somehow.
*/
public Citation(final String path, final char delimiter,
final String start, final String end) {
build(path, delimiter, start, end);
}
/**
* Builder for constructing an instance.
*
* @param path The pathname string for the file to quote. Not null.
* @param delimiter The delimiter for path, markers.
* @param start The start marker. If null or "", only path is useful.
* @param end The end marker. If null or "", {@code start} is both.
* @throws IllegalArgumentException Path or marker is broken somehow.
*/
private void build(final String path, final char delimiter,
final String start, final String end) {
setPath(path);
setDelimiter(delimiter);
setStart(start);
setEnd(end);
}
/**
* Returns the value of the path.
*
* @return The value of the path.
*/
public String getPath() {
return this.path;
}
/**
* Sets the pathname string for the file to quote.
*
* @param path The pathname string for the file to quote.
* @throws IllegalArgumentException Path cannot be null.
*/
public void setPath(final String path) {
if (path == null) {
throw new IllegalArgumentException("Path cannot be null.");
} else {
this.path = path;
}
}
/**
* Returns the value of the delimiter.
*
* @return The value of the delimiter.
*/
public char getDelimiter() {
return this.delimiter;
}
/**
* Sets the value of the delimiter.
*
* @param delimiter The delimiter for path, markers.
*/
public void setDelimiter(char delimiter) {
if (delimiter == ':' || delimiter == '%') {
this.delimiter = delimiter;
} else {
this.delimiter = ':';
}
}
/**
* Returns the value of the start marker, which can be empty.
*
* @return The value of the start marker, which can be empty.
*/
public String getStart() {
return this.start;
}
/**
* Sets the value of the start marker, which must not contain the delimiter.
*
* @param start The value of the start marker.
* @throws IllegalArgumentException Start marker contains the delimiter.
*/
public void setStart(String start) {
if (isNullOrEmpty(start)) {
this.start = "";
} else if (!start.contains(Character.toString(getDelimiter()))) {
this.start = start;
} else {
throw new IllegalArgumentException("Start marker: " + start
+ " contains delimiter: " + getDelimiter());
}
}
/**
* Returns the value of the start marker, which can be empty.
*
* @return The value of the start marker, which can be empty.
*/
public String getEnd() {
return this.end;
}
/**
* Sets the value of the end marker, which must not contain the delimiter.
*
* @param end The value of the end marker.
* @throws IllegalArgumentException End marker contains the delimiter.
*/
public void setEnd(String end) {
if (isNullOrEmpty(end)) {
this.end = getStart();
} else if (!end.contains(Character.toString(getDelimiter()))) {
this.end = end;
} else {
throw new IllegalArgumentException("End marker: " + end
+ " contains delimiter: " + getDelimiter());
}
}
/**
* Returns true if the string is null or the string is empty.
*
* @param string The string.
* @return Whether the string is null or empty.
*/
private static boolean isNullOrEmpty(final String string) {
return (string == null || string.isEmpty());
}
/**
* Returns the string representation of this citation.
*
* @return The string representation of this citation.
*/
public String toString() {
String start =
(isNullOrEmpty(getStart()) ? "" : getDelimiter() + getStart());
String end;
if (isNullOrEmpty(getStart()) || isNullOrEmpty(getEnd())) {
end = "";
} else {
end = getDelimiter() + getEnd();
}
return OPEN + getPath() + start + end + CLOSE;
}
/**
* Returns a Citation from the string representation.
*
* @param citation The string representation.
* @param delimiter One of ":" or "%".
* @return A Citation corresponding to the string representation,
* or null if the string representation does not parse.
*/
public static Citation valueOf(final String citation, final String delimiter) {
// No null delimiters.
if (delimiter == null) {
return null;
}
// No illegal delimiters.
if (!(delimiter.equals(":") || delimiter.equals("%"))) {
return null;
}
// OPEN marks the start of the citation string.
if (!citation.startsWith(OPEN)) {
return null;
}
// CLOSE marks the end of the citation string.
if (!citation.endsWith(CLOSE)) {
return null;
}
// Remove the OPEN & CLOSE wrappers.
String unwrapped = citation
.replaceFirst(Pattern.quote(OPEN), "")
.replaceFirst(Pattern.quote(CLOSE), "");
// Starting with a delimiter means path is null.
if (unwrapped.startsWith(delimiter)) {
return null;
}
// Delimiters should delimit actual values, not empty strings.
if (unwrapped.contains(delimiter + delimiter)) {
return null;
}
// Tokenize using the delimiter, as values do not contain the delimiter.
StringTokenizer st = new StringTokenizer(unwrapped, delimiter);
String path = null;
String start = null;
String end = null;
if (st.hasMoreTokens()) { // path
path = st.nextToken();
}
if (st.hasMoreTokens()) { // start
start = st.nextToken();
}
if (st.hasMoreTokens()) { // end
end = st.nextToken();
}
if (st.hasMoreTokens()) {
return null;
}
// Path must not be null.
if (isNullOrEmpty(path)) {
return null;
}
return new Citation(path, ':', start, end);
}
/**
* Returns a Citation from the string representation,
* assuming the default delimiter, {@code :}.
*
* @param citation The string representation.
* @return A Citation corresponding to the string representation,
* or null if the string representation does not parse.
*/
public static Citation valueOf(final String citation) {
return valueOf(citation, ":");
}
}