1 package org.apache.maven.doxia.macro.snippet;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import java.io.BufferedReader;
23 import java.io.IOException;
24 import java.io.InputStreamReader;
25 import java.net.URL;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.regex.Pattern;
29
30 import org.codehaus.plexus.util.StringUtils;
31 import org.codehaus.plexus.util.IOUtil;
32
33 /**
34 * Utility class for reading snippets.
35 *
36 * @version $Id$
37 */
38 public class SnippetReader
39 {
40 /** System-dependent EOL. */
41 private static final String EOL = System.getProperty( "line.separator" );
42
43 /** The source. */
44 private URL source;
45
46 /** The encoding of the source. */
47 private String encoding;
48
49 /**
50 * Constructor.
51 *
52 * @param src The source
53 * @param encoding The file encoding
54 */
55 public SnippetReader( URL src, String encoding )
56 {
57 this.source = src;
58 this.encoding = encoding;
59 }
60
61 /**
62 * Constructor.
63 *
64 * @param src The source
65 */
66 public SnippetReader( URL src )
67 {
68 this( src, null ) ;
69 }
70
71 /**
72 * Reads the snippet with given id.
73 *
74 * @param snippetId The id of the snippet.
75 * @return The snippet.
76 * @throws java.io.IOException if something goes wrong.
77 */
78 public StringBuffer readSnippet( String snippetId )
79 throws IOException
80 {
81 List<String> lines = readLines( snippetId );
82 int minIndent = minIndent( lines );
83 StringBuffer result = new StringBuffer();
84 for ( String line : lines )
85 {
86 result.append( line.substring( minIndent ) );
87 result.append( EOL );
88 }
89 return result;
90 }
91
92 /**
93 * Returns the minimal indent of all the lines in the given List.
94 *
95 * @param lines A List of lines.
96 * @return the minimal indent.
97 */
98 int minIndent( List<String> lines )
99 {
100 int minIndent = Integer.MAX_VALUE;
101 for ( String line : lines )
102 {
103 minIndent = Math.min( minIndent, indent( line ) );
104 }
105 return minIndent;
106 }
107
108 /**
109 * Returns the indent of the given line.
110 *
111 * @param line A line.
112 * @return the indent.
113 */
114 int indent( String line )
115 {
116 char[] chars = line.toCharArray();
117 int indent = 0;
118 for ( ; indent < chars.length; indent++ )
119 {
120 if ( chars[indent] != ' ' )
121 {
122 break;
123 }
124 }
125 return indent;
126 }
127
128 /**
129 * Reads the snippet and returns the lines in a List.
130 *
131 * @param snippetId The id of the snippet.
132 * @return A List of lines.
133 * @throws IOException if something goes wrong.
134 */
135 private List<String> readLines( String snippetId )
136 throws IOException
137 {
138 BufferedReader reader;
139 if ( encoding == null || "".equals( encoding ) )
140 {
141 reader = new BufferedReader( new InputStreamReader( source.openStream() ) );
142 }
143 else
144 {
145 reader = new BufferedReader( new InputStreamReader( source.openStream(), encoding ) );
146 }
147
148 List<String> lines = new ArrayList<String>();
149 try
150 {
151 boolean capture = false;
152 String line;
153 boolean foundStart = false;
154 boolean foundEnd = false;
155 boolean hasSnippetId = StringUtils.isNotEmpty( snippetId );
156 while ( ( line = reader.readLine() ) != null )
157 {
158 if ( !hasSnippetId )
159 {
160 lines.add( line );
161 }
162 else
163 {
164 if ( isStart( snippetId, line ) )
165 {
166 capture = true;
167 foundStart = true;
168 }
169 else if ( isEnd( snippetId, line ) )
170 {
171 foundEnd = true;
172 break;
173 }
174 else if ( capture )
175 {
176 lines.add( line );
177 }
178 }
179 }
180
181 if ( hasSnippetId && !foundStart )
182 {
183 throw new IOException( "Failed to find START of snippet " + snippetId + " in file at URL: " + source );
184 }
185 if ( hasSnippetId && !foundEnd )
186 {
187 throw new IOException( "Failed to find END of snippet " + snippetId + " in file at URL: " + source );
188 }
189 }
190 finally
191 {
192 IOUtil.close( reader );
193 }
194 return lines;
195 }
196
197 /**
198 * Determines if the given line is a start demarcator.
199 *
200 * @param snippetId the id of the snippet.
201 * @param line the line.
202 * @return True, if the line is a start demarcator.
203 */
204 protected boolean isStart( String snippetId, String line )
205 {
206 return isDemarcator( snippetId, "START", line );
207 }
208
209 /**
210 * Determines if the given line is a demarcator.
211 *
212 * @param snippetId the id of the snippet.
213 * @param what Identifier for the demarcator.
214 * @param line the line.
215 * @return True, if the line is a start demarcator.
216 */
217 protected static boolean isDemarcator( String snippetId, String what, String line )
218 {
219 // SNIPPET and what are case insensitive
220 // SNIPPET and what can switch order
221 String snippetRegExp = "(^|\\W)(?i:SNIPPET)($|\\W)";
222 String snippetIdRegExp = "(^|\\W)" + snippetId + "($|\\W)";
223 String whatRegExp = "(^|\\W)(?i:" + what + ")($|\\W)";
224
225 return Pattern.compile( snippetRegExp ).matcher( line ).find()
226 && Pattern.compile( whatRegExp ).matcher( line ).find()
227 && Pattern.compile( snippetIdRegExp ).matcher( line ).find();
228 }
229
230 /**
231 * Determines if the given line is an end demarcator.
232 *
233 * @param snippetId the id of the snippet.
234 * @param line the line.
235 * @return True, if the line is an end demarcator.
236 */
237 protected boolean isEnd( String snippetId, String line )
238 {
239 return isDemarcator( snippetId, "END", line );
240 }
241 }