View Javadoc
1   /*
2    * dpkg - Debian Package library and the Debian Package Maven plugin
3    * (c) Copyright 2016 Gerrit Hohl
4    *
5    * This program is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU General Public License
7    * as published by the Free Software Foundation; either version 2
8    * of the License, or (at your option) any later version.
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with this program; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18   */
19  package net.sourceforge.javadpkg.impl;
20  
21  import java.io.BufferedReader;
22  import java.io.IOException;
23  import java.io.InputStreamReader;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Date;
27  import java.util.List;
28  
29  import net.sourceforge.javadpkg.ChangeLog;
30  import net.sourceforge.javadpkg.ChangeLogConstants;
31  import net.sourceforge.javadpkg.ChangeLogParser;
32  import net.sourceforge.javadpkg.ChangeLogUrgency;
33  import net.sourceforge.javadpkg.ChangeLogUrgencyParser;
34  import net.sourceforge.javadpkg.ChangeLogVersionEntryDetail;
35  import net.sourceforge.javadpkg.Context;
36  import net.sourceforge.javadpkg.ParseException;
37  import net.sourceforge.javadpkg.control.PackageMaintainer;
38  import net.sourceforge.javadpkg.control.PackageMaintainerParser;
39  import net.sourceforge.javadpkg.control.PackageName;
40  import net.sourceforge.javadpkg.control.PackageNameParser;
41  import net.sourceforge.javadpkg.control.PackageVersion;
42  import net.sourceforge.javadpkg.control.PackageVersionParser;
43  import net.sourceforge.javadpkg.control.impl.PackageMaintainerParserImpl;
44  import net.sourceforge.javadpkg.control.impl.PackageNameParserImpl;
45  import net.sourceforge.javadpkg.control.impl.PackageVersionParserImpl;
46  import net.sourceforge.javadpkg.io.DataSource;
47  
48  
49  /**
50   * <p>
51   * A {@link ChangeLogParser} implementation.
52   * </p>
53   *
54   * @author Gerrit Hohl (gerrit-hohl@users.sourceforge.net)
55   * @version <b>1.0</b>, 04.05.2016 by Gerrit Hohl
56   */
57  public class ChangeLogParserImpl implements ChangeLogParser, ChangeLogConstants {
58  	
59  	
60  	/** The parser for the package name. */
61  	private PackageNameParser		packageNameParser;
62  	/** The parser for the version. */
63  	private PackageVersionParser	packageVersionParser;
64  	/** The parser for the urgency. */
65  	private ChangeLogUrgencyParser	changeLogUrgencyParser;
66  	/** The parser for the maintainer. */
67  	private PackageMaintainerParser	packageMaintainerParser;
68  	
69  	
70  	/**
71  	 * <p>
72  	 * Creates a parser.
73  	 * </p>
74  	 */
75  	public ChangeLogParserImpl() {
76  		super();
77  		
78  		this.packageNameParser = new PackageNameParserImpl();
79  		this.packageVersionParser = new PackageVersionParserImpl();
80  		this.changeLogUrgencyParser = new ChangeLogUrgencyParserImpl();
81  		this.packageMaintainerParser = new PackageMaintainerParserImpl();
82  	}
83  	
84  	
85  	@Override
86  	public ChangeLog parseChangeLog(DataSource source, Context context) throws IOException, ParseException {
87  		ChangeLogImpl changeLog;
88  		String line = null;
89  		int lineNumber = 0;
90  		ChangeLogVersionEntryImpl entry = null;
91  		StringBuilder sb = null;
92  		ChangeLogVersionEntryDetail detail;
93  
94  
95  		if (source == null)
96  			throw new IllegalArgumentException("Argument source is null.");
97  		if (context == null)
98  			throw new IllegalArgumentException("Argument context is null.");
99  		
100 		changeLog = new ChangeLogImpl();
101 		try {
102 			try (BufferedReader in = new BufferedReader(new InputStreamReader(source.getInputStream(), UTF_8_CHARSET))) {
103 				while ((line = in.readLine()) != null) {
104 					lineNumber++;
105 
106 					// --- Ignore comments --
107 					if ("#".equals(line) || line.startsWith("# ")) {
108 						continue;
109 					} else
110 					// --- Is this the first line of a version block? ---
111 					if (entry == null) {
112 						// --- Ignore empty lines between entries ---
113 						if (line.isEmpty() || line.trim().isEmpty()) {
114 							continue;
115 						}
116 						
117 						entry = this.parseInitialLine(line, context);
118 						if (entry == null) {
119 							break;
120 						}
121 						changeLog.addEntry(entry);
122 					}
123 					// --- Is this the last line of a version block? ---
124 					else if (line.startsWith(TERMINATION_PREFIX)) {
125 						// --- Do we have some detail text left? ---
126 						if (sb != null) {
127 							// --- Add the detail ---
128 							detail = new ChangeLogVersionEntryDetailImpl(sb.toString());
129 							entry.addDetail(detail);
130 							sb = null;
131 						}
132 						this.parseTerminationLine(line, entry, context);
133 						entry = null;
134 					}
135 					// --- Is this a new detail of the current version entry? ---
136 					else if (line.startsWith(DETAIL_START_PREFIX)) {
137 						// --- Do we have some detail text left? ---
138 						if (sb != null) {
139 							// --- Add the detail ---
140 							detail = new ChangeLogVersionEntryDetailImpl(sb.toString());
141 							entry.addDetail(detail);
142 							sb = null;
143 						}
144 						sb = this.parseDetailLine(line, null, context);
145 					}
146 					// --- Is this an additional line of the current detail? ---
147 					else if (line.startsWith(DETAIL_LINE_PREFIX)) {
148 						if (sb == null) {
149 							context.addWarning(new ChangeLogUnsupportedLineWarning(line));
150 						} else {
151 							sb = this.parseDetailLine(line, sb, context);
152 						}
153 					}
154 					// --- Empty lines are stripped away. But is this line empty? ---
155 					else if (!line.isEmpty()) {
156 						context.addWarning(new ChangeLogUnsupportedLineWarning(line));
157 					}
158 				}
159 				// --- Did we not reach the end? ---
160 				if (entry != null)
161 					throw new ParseException("End of last version entry in the change log is missing.");
162 				if (lineNumber == 0)
163 					throw new ParseException("Change log is empty.");
164 			}
165 		} catch (IOException e) {
166 			throw new IOException("An error occurred while parsing source |" + source.getName() + "|: " + e.getMessage(), e);
167 		} catch (ParseException e) {
168 			throw new ParseException(
169 					"Line |" + line + "| (Line number: " + lineNumber + ") couldn't be parsed: " + e.getMessage(), e);
170 		}
171 		return changeLog;
172 	}
173 	
174 	
175 	/**
176 	 * <p>
177 	 * Parses the initial line of a version.
178 	 * </p>
179 	 *
180 	 * @param line
181 	 *            The line.
182 	 * @param context
183 	 *            The context.
184 	 * @return The entry containing the content of the first line.
185 	 * @throws ParseException
186 	 *             If an error occurs while parsing.
187 	 */
188 	private ChangeLogVersionEntryImpl parseInitialLine(String line, Context context) throws ParseException {
189 		ChangeLogVersionEntryImpl entry;
190 		int index, lastIndex;
191 		String value;
192 		PackageName packageName;
193 		PackageVersion version;
194 		List<String> distributions;
195 		ChangeLogUrgency urgency;
196 		
197 		
198 		// --- Package name ---
199 		index = line.indexOf(' ');
200 		if (index == -1) {
201 			context.addWarning(new ChangeLogInitialLineUnsupportedWarning(line,
202 					"Didn't find first whitespace in the first line of the change log entry."));
203 			return null;
204 		}
205 		value = line.substring(0, index);
206 		try {
207 			packageName = this.packageNameParser.parsePackageName(value, context);
208 		} catch (ParseException e) {
209 			context.addWarning(new ChangeLogInitialLineUnsupportedWarning(line,
210 					"Couldn't parser package name |" + value + "| of first line of change log entry: " + e.getMessage()));
211 			return null;
212 		}
213 		
214 		// --- Version ---
215 		index++;
216 		if (line.length() <= index) {
217 			context.addWarning(new ChangeLogInitialLineUnsupportedWarning(line,
218 					"First line of change log entry is too short. Only found the package name."));
219 			return null;
220 		}
221 		if (line.charAt(index) != '(') {
222 			context.addWarning(new ChangeLogInitialLineUnsupportedWarning(line,
223 					"Couldn't find the beginning of version of the package in the first line of change log entry."));
224 			return null;
225 		}
226 		lastIndex = ++index;
227 		index = line.indexOf(')', lastIndex);
228 		if (index == -1) {
229 			context.addWarning(new ChangeLogInitialLineUnsupportedWarning(line,
230 					"Couldn't find the end of version of the package in the first line of change log entry."));
231 			return null;
232 		}
233 		value = line.substring(lastIndex, index);
234 		try {
235 			version = this.packageVersionParser.parsePackageVersion(value, context);
236 		} catch (ParseException e) {
237 			context.addWarning(new ChangeLogInitialLineUnsupportedWarning(line,
238 					"Couldn't parser version |" + value + "| of first line of change log entry: " + e.getMessage()));
239 			return null;
240 		}
241 		lastIndex = index + 1;
242 		
243 		// --- Distributions ---
244 		index = line.indexOf(';', lastIndex);
245 		if (index == -1) {
246 			context.addWarning(new ChangeLogInitialLineUnsupportedWarning(line,
247 					"Couldn't find the semi-colon between the distributions and the urgency in the first line of change log entry."));
248 			return null;
249 		}
250 		value = line.substring(lastIndex, index).trim();
251 		distributions = new ArrayList<>(Arrays.asList(value.split(" ")));
252 		lastIndex = index + 1;
253 		
254 		// --- Urgency ---
255 		value = line.substring(lastIndex).trim();
256 		if (!value.startsWith("urgency=")) {
257 			context.addWarning(
258 					new ChangeLogInitialLineUnsupportedWarning(line, "Couldn't the urgency prefix |urgency=| in the part |"
259 							+ value + "| after the semi-colon in the first line of change log entry."));
260 			return null;
261 		}
262 		value = value.substring(8);
263 		try {
264 			urgency = this.changeLogUrgencyParser.parseChangeLogUrgency(value, context);
265 		} catch (ParseException e) {
266 			context.addWarning(new ChangeLogInitialLineUnsupportedWarning(line,
267 					"Couldn't parser urgency |" + value + "| of first line of change log entry: " + e.getMessage()));
268 			return null;
269 		}
270 		
271 		entry = new ChangeLogVersionEntryImpl();
272 		entry.setPackageName(packageName);
273 		entry.setVersion(version);
274 		entry.setDistributions(distributions);
275 		entry.setUrgency(urgency);
276 		return entry;
277 	}
278 
279 
280 	/**
281 	 * <p>
282 	 * Parses a line of a detail.
283 	 * </p>
284 	 *
285 	 * @param line
286 	 *            The line.
287 	 * @param detail
288 	 *            The current detail (optional).
289 	 * @param context
290 	 *            The context.
291 	 * @return The current detail.
292 	 * @throws ParseException
293 	 *             If an error occurs while parsing.
294 	 */
295 	private StringBuilder parseDetailLine(String line, StringBuilder detail, Context context) throws ParseException {
296 		StringBuilder sb;
297 		String value;
298 
299 
300 		if (detail == null) {
301 			sb = new StringBuilder();
302 		} else {
303 			sb = detail;
304 		}
305 		value = line.substring(4);
306 		if (sb.length() > 0) {
307 			sb.append('\n');
308 		}
309 		// --- Only add the value if it is not just an empty line ---
310 		if (!".".equals(value)) {
311 			sb.append(value);
312 		}
313 
314 		return sb;
315 	}
316 
317 
318 	/**
319 	 * <p>
320 	 * Parses the termination line of a version.
321 	 * </p>
322 	 *
323 	 * @param line
324 	 *            The line.
325 	 * @param entry
326 	 *            The entry.
327 	 * @param context
328 	 *            The context.
329 	 * @throws ParseException
330 	 *             If an error occurs while parsing.
331 	 */
332 	private void parseTerminationLine(String line, ChangeLogVersionEntryImpl entry, Context context) throws ParseException {
333 		int index;
334 		String rest, value;
335 		PackageMaintainer maintainer;
336 		Date date;
337 
338 
339 		rest = line.substring(TERMINATION_PREFIX.length());
340 		index = rest.indexOf(MAINTAINER_TIMESTAMP_SEPARATOR);
341 		if (index == -1)
342 			throw new ParseException("Couldn't parse last line |" + line
343 					+ "| of change log entry as the separator between maintainer and timestamp couldn't be found.");
344 		
345 		// --- Maintainer ---
346 		value = rest.substring(0, index);
347 		try {
348 			maintainer = this.packageMaintainerParser.parsePackageMaintainer(value, context);
349 		} catch (ParseException e) {
350 			throw new ParseException(
351 					"Couldn't parser maintainer |" + value + "| of last line of change log entry: " + e.getMessage(), e);
352 		}
353 		
354 		// --- Timestamp ---
355 		value = rest.substring(index + 2);
356 		try {
357 			date = TIMESTAMP_FORMAT.parse(value);
358 		} catch (java.text.ParseException e) {
359 			throw new ParseException(
360 					"Couldn't parse timestamp |" + value + "| of last line of change log entry: " + e.getMessage(), e);
361 		}
362 
363 		entry.setMaintainer(maintainer);
364 		entry.setDate(date);
365 	}
366 
367 
368 	@Override
369 	public ChangeLog parseChangeLogHtml(DataSource source, Context context) throws IOException, ParseException {
370 		// TODO Implement method.
371 		throw new ParseException("Not yet implemented");
372 	}
373 	
374 	
375 }