View Javadoc
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-2015 ForgeRock AS.
15   */
16  
17  package org.forgerock.maven.plugins.xcite;
18  
19  import org.forgerock.maven.plugins.xcite.utils.FileUtils;
20  import org.forgerock.maven.plugins.xcite.utils.StringUtils;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.util.ArrayList;
25  import java.util.regex.Matcher;
26  import java.util.regex.Pattern;
27  
28  /**
29   * Resolve citation strings in target files into quotes from source files.
30   */
31  public class Resolver {
32  
33      private boolean                         escapeXml;
34      private String                          indent;
35      private boolean                         outdent;
36      private File                            outputDirectory;
37  
38      /**
39       * Construct a resolver.
40       *
41       * @param outputDirectory   Where to write files with quotes.
42       *                          Specify the source directory to replace files.
43       * @param escapeXml         Escape XML when quoting.
44       * @param indent            Indent quotes by this number of single spaces.
45       * @param outdent           Outdent to the left margin.
46       *                          When you specify
47       *                          both {@code indent} and {@code outdent},
48       *                          quotes are first outdented, then indented.
49       */
50      Resolver(File outputDirectory, boolean escapeXml, int indent, boolean outdent) {
51          this.outputDirectory    = outputDirectory;
52          this.escapeXml          = escapeXml;
53          this.indent             = new String(new char[indent]).replace('\0', ' ');
54          this.outdent            = outdent;
55      }
56  
57      /**
58       * Resolve citation strings in a set of target files.
59       *
60       * @param sourceDirectory   Where to find files with citations.
61       * @param files             Relative file paths.
62       * @throws IOException      Failed to read or write a file.
63       */
64      void resolve(File sourceDirectory, String[] files) throws IOException {
65          for (String relativePath: files) {
66              resolve(sourceDirectory, new File(relativePath));
67          }
68      }
69  
70      /**
71       * Resolve citation strings in a target file.
72       *
73       * @param baseDir       Where to find the file.
74       * @param file          Relative path to file.
75       * @throws IOException  Failed to read or write the file.
76       */
77      void resolve(File baseDir, File file) throws IOException {
78          File absFile = new File(baseDir, file.getPath());
79  
80          if (!absFile.isFile()) {
81              return;
82          }
83  
84          StringBuilder stringBuilder = new StringBuilder();
85          String prefix = "";
86          for (String line: FileUtils.getStrings(absFile)) {
87              stringBuilder.append(prefix);
88              prefix = System.getProperty("line.separator");
89              stringBuilder.append(resolve(absFile, line));
90          }
91  
92          File out = new File(outputDirectory, file.getPath());
93          org.codehaus.plexus.util.FileUtils.fileWrite(
94                  out, stringBuilder.toString());
95      }
96  
97      /**
98       * Resolve citation strings a line of text.
99       *
100      * @param file          Absolute path to current file.
101      * @param line          Line potentially containing citations.
102      * @return              Line with quotes resolved.
103      * @throws IOException  Failed to process the line.
104      */
105     String resolve(File file, String line) throws IOException {
106 
107         // Split the line into parts, where some are citations, some not.
108         String[] parts = split(line);
109 
110         // For part that are citations, replace them with quotes.
111         int i = 0;
112         for (String part: parts) {
113             if (part == null) {
114                 parts[i] = "";
115                 ++i;
116                 continue;
117             }
118 
119             // Try to construct a Citation with both delimiters,
120             // the alternative % and also the default :.
121             Citation citation = null;
122             if (part.contains("%")) {
123                 citation = Citation.valueOf(part, "%");
124             }
125             if (citation == null) {
126                 citation = Citation.valueOf(part);
127             }
128 
129             if (citation == null) { // The part is not a citation.
130                 parts[i] = part;
131             } else {
132                 String quote = getQuote(file, citation);
133 
134                 // If the quote is the same the original citation string,
135                 // return the part unchanged.
136                 parts[i] = (quote.equals(citation.toString())) ? part : quote;
137             }
138 
139             ++i;
140         }
141 
142         // Put the line back together into a single string.
143         StringBuilder stringBuilder = new StringBuilder();
144         for (String part: parts) {
145             stringBuilder.append(part);
146         }
147         return stringBuilder.toString();
148     }
149 
150     /**
151      * Split a line into strings, where citation strings are separate.
152      *
153      * @param line The line to split.
154      * @return     The line split into strings. Null for null input.
155      */
156     String[] split(String line) {
157         if (line == null) {
158             return null;
159         }
160 
161         if (line.isEmpty()) {
162             return new String[1];
163         }
164 
165         ArrayList<String> parts = new ArrayList<String>();
166 
167         // The line is composed of parts,
168         // possibly with text preceding each citation,
169         // possibly with trailing text following the last citation.
170         // For example, "pre [/test] [/test] post" splits into
171         // ["pre ", "[/test]", " ", "[/test]", " post"].
172 
173         // open-bracket 1*(exclude close-bracket) close-bracket
174         Pattern citationCandidate = Pattern.compile("(\\[[^\\]]+\\])");
175         Matcher matcher = citationCandidate.matcher(line);
176         int index = 0;
177         while (matcher.find()) {
178             String before = line.substring(index, matcher.start());
179             if (!before.isEmpty()) {
180                 parts.add(before);
181             }
182             parts.add(matcher.group());
183             index = matcher.end();
184         }
185         if (index < line.length()) {
186             parts.add(line.substring(index));
187         }
188 
189         String[] results = new String[parts.size()];
190         return parts.toArray(results);
191     }
192 
193     /**
194      * Return the quote for a Citation in the specified file.
195      *
196      * @param file          The file where the citation is found.
197      * @param citation      The citation to resolve.
198      * @return              The quote from the resolved citation.
199      * @throws IOException  Failed to read the quote from the file.
200      */
201     String getQuote(File file, Citation citation) throws IOException {
202 
203         // Citations can have relative paths for the files they cite.
204         // Get an absolute path instead in order to read the quote file.
205         File citedFile = new File(citation.getPath());
206         if (!citedFile.isAbsolute()) {
207             String currentDirectory = file.getParent();
208             citedFile = new File(currentDirectory, citedFile.getPath());
209         }
210 
211         // Either this is not a citation, or it is a broken citation.
212         if (!citedFile.exists() || !citedFile.isFile()) {
213             return citation.toString();
214         }
215 
216         // Extract the raw quote from the cited file.
217         ArrayList<String> quoteLines = StringUtils.extractQuote(
218                 FileUtils.getStrings(citedFile), citation.getStart(), citation.getEnd());
219 
220         if (escapeXml) {
221             quoteLines = StringUtils.escapeXml(quoteLines);
222         }
223 
224         if (outdent) {
225             quoteLines = StringUtils.outdent(quoteLines);
226         }
227 
228         if (!indent.isEmpty()) {
229             quoteLines = StringUtils.indent(quoteLines, indent);
230         }
231 
232         // Quotes can contain citations.
233         // Resolve any citations in the quote before returning it as a string.
234         StringBuilder stringBuilder = new StringBuilder();
235         String prefix = "";
236         for (String quoteLine: quoteLines) {
237             stringBuilder.append(prefix);
238             prefix = System.getProperty("line.separator");
239             stringBuilder.append(resolve(citedFile, quoteLine)); // TODO: loop?
240         }
241         return stringBuilder.toString();
242     }
243 }