View Javadoc

1   //Copyright 2007-2008, by the California Institute of Technology.
2   //ALL RIGHTS RESERVED. United States Government Sponsorship acknowledged.
3   //Any commercial use must be negotiated with the Office of Technology Transfer
4   //at the California Institute of Technology.
5   //
6   //This software is subject to U. S. export control laws and regulations
7   //(22 C.F.R. 120-130 and 15 C.F.R. 730-774). To the extent that the software
8   //is subject to U.S. export control laws and regulations, the recipient has
9   //the responsibility to obtain export licenses or other export authority as
10  //may be required before exporting such information to foreign countries or
11  //providing access to foreign nationals.
12  
13  package gov.nasa.pds.ltdt.parser;
14  
15  import gov.nasa.pds.ltdt.label.statement.PrettyAttributeStatement;
16  import gov.nasa.pds.ltdt.label.statement.PrettyCommentStatement;
17  import gov.nasa.pds.ltdt.label.statement.PrettyGroupStatement;
18  import gov.nasa.pds.ltdt.label.statement.PrettyObjectStatement;
19  import gov.nasa.pds.ltdt.label.statement.Value;
20  import gov.nasa.pds.tools.label.MalformedSFDULabel;
21  import gov.nasa.pds.tools.label.SFDULabel;
22  
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.InputStreamReader;
26  import java.io.LineNumberReader;
27  import java.util.ArrayList;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.regex.Matcher;
31  import java.util.regex.Pattern;
32  
33  /***
34   * Class that parses a PDS label. This class does not do any semantic
35   * validation of values.
36   *
37   */
38  public class ParserLite {
39  	
40  	private static final String COMMENTS_EXPRESSION = "///*.*//*/";
41  	private static final String COMMENT_ID = "COMMENT-";
42  	private LineNumberReader reader;
43  	private boolean endStatementFound;
44  	private boolean endOfFileReached;
45  	
46  	public ParserLite() {
47  		endStatementFound = false;
48  		endOfFileReached = false;
49  	}
50  	
51  	/***
52  	 * Parse the PDS label
53  	 * 
54  	 * @param label A stream representation of a PDS label. 
55  	 * @return A list that can contain PrettyStatement objects, and
56  	 *  string representations of a blank line, SFDU, or the END statement
57  	 *  in a label. 
58  	 * 
59  	 * @throws IOException
60  	 */
61  	public List parse(InputStream label) throws IOException {
62  		//TODO: For now, blank lines, SFDUs and the END statement
63  		//are stored as strings in the resulting list.
64  		List statements = new ArrayList();
65  				
66  		label.mark(20);
67  		List sfdus = consumeSFDU(label);
68  		if(!sfdus.isEmpty()) {
69  			String sfdu ="";
70  			for(Iterator i=sfdus.iterator(); i.hasNext();) {
71  				//For now, store the sfdus as-is, if any were found
72  				sfdu += i.next().toString();
73  			}
74  			statements.add(sfdu);
75  			//Skip carriage-return line feed as well
76  			label.skip(2);
77  		}
78  		else
79  			label.reset();
80  
81  		reader = new LineNumberReader(new InputStreamReader(label));
82  		
83  		while( !(endStatementFound || endOfFileReached) ) {
84  			List results = getNextLine();
85  			for(Iterator i = results.iterator(); i.hasNext();) {
86  				Object result = i.next();
87  				if(result instanceof PrettyAttributeStatement) {
88  					PrettyAttributeStatement stmt = (PrettyAttributeStatement) result;
89  					String identifier = stmt.getIdentifier();
90  					if(identifier.equals("OBJECT")) {
91  						String value = stmt.getValue().toString();
92  						PrettyObjectStatement object = 
93  							new PrettyObjectStatement(value + "_Line_" + reader.getLineNumber());
94  						object = getNestedStatements(object);
95  						statements.add(object);
96  					}
97  					else if(identifier.equals("GROUP")) {
98  						String value = stmt.getValue().toString();
99  						PrettyGroupStatement group = 
100 							new PrettyGroupStatement(value + "_Line_" + reader.getLineNumber());
101 						group = getNestedStatements(group);
102 						statements.add(group);
103 					}
104 					else
105 						statements.add(stmt);
106 				}
107 				else
108 					statements.add(result);
109 			}
110 		}
111 		//Check for a final SFDU following the END statement.
112 		try {
113 			sfdus = consumeFinalSFDU();
114 			if(!sfdus.isEmpty()) {
115 				String sfdu ="";
116 				for(Iterator i=sfdus.iterator(); i.hasNext();) {
117 					sfdu += i.next().toString();
118 				}
119 				statements.add(sfdu);
120 			}
121 		} catch(IOException i) {
122 			//Don't do anything
123 		} finally {
124 			reader.close();
125 		}
126 		return statements;
127 	}
128 	
129 	/***
130 	 * Get the next line in a PDS label. Any comments found
131 	 * within a line gets parsed and stored first before attempting
132 	 * to find the next valid ODL statement (KEYWORD = VALUE).
133 	 * 
134 	 * @return A list of statements.
135 	 * 
136 	 * @throws IOException
137 	 */
138 	public List getNextLine() throws IOException {
139 		List result = new ArrayList();
140 		String savedText = "";
141 		String text = null;
142 		boolean isStatement = false;
143 		
144 		while(!isStatement) {
145 			try {
146 				if((text = reader.readLine()) == null) {
147 					endOfFileReached = true;
148 					throw new EndOfFileException("End of file reached.");
149 				}
150 				text = savedText + " " + text;
151 				text = text.replaceAll("//s+", " ").trim();
152 				if(savedText.equals("") && text.matches("")) {
153 					throw new BlankLineFoundException("Blank Line found");
154 				}
155 				//Find comments, make sure they are not within quotes, save,
156 				//and remove them before attempting to parse a statement
157 				Pattern commentExpression = Pattern.compile(COMMENTS_EXPRESSION);
158 				Matcher commentMatcher = commentExpression.matcher(text);
159 				while(commentMatcher.find()) {
160 					int start = commentMatcher.start();
161 					int end = commentMatcher.end();
162 					Pattern quotesExpression = Pattern.compile("\"");
163 					Matcher quoteMatcher = quotesExpression.matcher(text.substring(0, start));
164 					int quoteCount = 0;
165 					while(quoteMatcher.find()) {
166 						++quoteCount;
167 					}
168 					//If quotes are found and it is an even number of quotes, then
169 					//we can go ahead and store the comments, then remove it
170 					if(quoteCount % 2 == 0) {
171 						//Create a dummy identifier for the comment
172 						String id = COMMENT_ID + reader.getLineNumber();
173 						String comment = text.substring(start+2, end-2).trim();
174 						PrettyCommentStatement cs = new PrettyCommentStatement(-1, id, comment);
175 						result.add(cs);
176 						text = new StringBuffer(text).delete(start, end).toString();
177 					}
178 				}		
179 				if(text.trim().equals("END")) {
180 					isStatement = true;
181 					endStatementFound = true;
182 				}
183 				else {
184 					// Find a keyword-value pair
185 					String []statement = text.split("=", 2);
186 					if(statement.length <= 1 || (statement[1].matches("//s+") || statement[1].matches("")))
187 						throw new StatementNotFoundException("Statement not found");
188 
189 					String value = statement[1].trim();
190 					// If the value starts with a double-quote, parenthesis or brace,
191 					// Then it is possible that this is a multi-line value
192 					if(value.matches("[\"{(].*")) {
193 						checkValue(value);
194 					}
195 					isStatement = true;				
196 				}
197 			} catch(EndOfFileException e) {
198 				if(!("".equals(savedText)))
199 					result.add(savedText);
200 				break;
201 			} catch(BadValueException be) {
202 				savedText = text;
203 			} catch (StatementNotFoundException se) {
204 				savedText = text;
205 			} catch (BlankLineFoundException e) {
206 				result.add(" ");
207 			}
208 		}
209 		//Once we form a possible statement, we can begin parsing
210 		if(isStatement)
211 			result.add(parseStatement(text));
212 		
213 		return result;
214 	}
215 
216 	
217 	/***
218 	 * Parse a statement
219 	 * 
220 	 * @param string A string representation of a valid ODL statement.
221 	 * @return an AttributeStatement object or null if no statement
222 	 * was found.
223 	 */
224 	public PrettyAttributeStatement parseStatement(String string) {
225 		PrettyAttributeStatement stmt = null;
226 		String []statement = string.split("=", 2);
227 		String identifier = statement[0].trim();
228 		try {
229 			Value value = new Value(statement[1].trim());
230 			stmt = new PrettyAttributeStatement(identifier, value);
231 		} catch(IndexOutOfBoundsException i) {
232 			stmt = new PrettyAttributeStatement(identifier);
233 		}
234 		return stmt;
235 	}
236 	
237 	/***
238 	 * Gets the statements found within an OBJECT.
239 	 * 
240 	 * @param object The parent OBJECT.
241 	 * @return The OBJECT, complete with its attributes.
242 	 * 
243 	 * @throws IOException 
244 	 */
245 	private PrettyObjectStatement getNestedStatements(PrettyObjectStatement object)
246 	                                                             throws IOException {
247 		boolean endOfObject = false;
248 		
249 		while(!endOfObject && !endOfFileReached) {
250 			List results = getNextLine();
251 			for(Iterator i=results.iterator(); i.hasNext();) {
252 				Object result = i.next();
253 				try {
254 					PrettyAttributeStatement ps = (PrettyAttributeStatement) result;			
255 					if(ps.getIdentifier().equals("END_OBJECT"))
256 						endOfObject = true;
257 					else if(ps.getIdentifier().equals("OBJECT")) {
258 						String identifier = ps.getValue().toString();
259 						PrettyObjectStatement nestedObject = 
260 							new PrettyObjectStatement(identifier + "_Line_" + reader.getLineNumber());
261 						nestedObject = getNestedStatements(nestedObject);
262 						object.addStatement(nestedObject);
263 					}
264 					else if(ps.getIdentifier().equals("GROUP")) {
265 						String identifier = ps.getValue().toString();
266 						PrettyGroupStatement nestedGroup = 
267 							new PrettyGroupStatement(identifier + "_Line_" + reader.getLineNumber());
268 						nestedGroup = getNestedStatements(nestedGroup);
269 						object.addStatement(nestedGroup);
270 					}
271 					else
272 						object.addStatement(ps);
273 				} catch(ClassCastException c1) {
274 					try {
275 						PrettyCommentStatement cs = (PrettyCommentStatement) result;
276 						object.addStatement(cs);
277 					}catch(ClassCastException c2) {
278 						//We have something within the object that must have not gotten
279 						//parsed correctly. We will create an AttributeStatementString
280 						//object and use the string as the identifier so that we can
281 						//store this in the object and print it out.
282 						if(!result.toString().matches("//s+"))
283 							object.addStatement(new PrettyAttributeStatement(result.toString()));
284 					}
285 				}
286 			}
287 		}
288 		return object;
289 	}
290 	
291 	/***
292 	 * Gets the statements found within a GROUP object.
293 	 * 
294 	 * @param group The parent GROUP object.
295 	 * @return The GROUP object, complete with its attributes.
296 	 * 
297 	 * @throws IOException
298 	 */
299 	private PrettyGroupStatement getNestedStatements(PrettyGroupStatement group)
300 	                                                         throws IOException {
301 		boolean endOfGroup = false;
302 		
303 		while(!endOfGroup && !endOfFileReached) {
304 			List results = getNextLine();
305 			for(Iterator i=results.iterator(); i.hasNext();) {
306 				Object result = i.next();
307 				try {
308 					PrettyAttributeStatement ps = (PrettyAttributeStatement) result;
309 					//We shouldn't be allowing objects or groups wtihin a
310 					//group object, but this isn't a validation method.
311 					if(ps.getIdentifier().equals("OBJECT")) {
312 						String identifier = ps.getValue().toString();
313 						PrettyObjectStatement nestedObject = 
314 							new PrettyObjectStatement(identifier + "_Line_" + reader.getLineNumber());
315 						nestedObject = getNestedStatements(nestedObject);
316 						group.addStatement(nestedObject);
317 					}
318 					else if(ps.getIdentifier().equals("GROUP")) {
319 						String identifier = ps.getValue().toString();
320 						PrettyGroupStatement nestedGroup = 
321 							new PrettyGroupStatement(identifier + "_Line_" + reader.getLineNumber());
322 						nestedGroup = getNestedStatements(nestedGroup);
323 						group.addStatement(nestedGroup);
324 					}
325 					else if(ps.getIdentifier().equals("END_GROUP"))
326 						endOfGroup = true;
327 					else
328 						group.addStatement(ps);
329 					
330 				} catch(ClassCastException c1) {
331 					try {
332 						PrettyCommentStatement cs = (PrettyCommentStatement) result;
333 						group.addStatement(cs);
334 					} catch(ClassCastException c2) {
335 						//We have something within the object that must have not gotten
336 						//parsed correctly. We will create an AttributeStatementString
337 						//object and use the string as the identifier so that we can
338 						//store this in the object and print it out.
339 						if(!result.toString().matches("//s+"))
340 							group.addStatement(new PrettyAttributeStatement(result.toString()));
341 					}
342 				} 	
343 			}
344 		}
345 		return group;
346 	}
347 	
348     private List consumeSFDU(InputStream input) throws IOException {
349         List sfdus = new ArrayList();
350         
351         byte[] sfduLabel = new byte[20];
352         int count = input.read(sfduLabel);
353         if (count == 20) {
354             try {
355                 SFDULabel sfdu = new SFDULabel(sfduLabel);
356                 if ("CCSD".equals(sfdu.getControlAuthorityId())) {
357                     sfdus.add(sfdu);
358                     //Read in second SFDU label
359                     input.read(sfduLabel);
360                     sfdus.add(new SFDULabel(sfduLabel));
361                 }
362             } catch (MalformedSFDULabel e) {
363                 //For now we can ignore this error as there is likely not a header.
364             }   
365         }      
366         return sfdus;
367     }
368 	
369     /***
370      * Consumes the final SFDU statement, if it is found after the END
371      * statement.
372      * 
373      * @return the SFDUs, if any.
374      * @throws IOException
375      */
376     private List consumeFinalSFDU() throws IOException {
377     	List sfdus = new ArrayList();
378     	
379     	char[] sfduLabel = new char[20];
380     	int count = reader.read(sfduLabel);
381     	if(count == 20) {
382     		try {
383     			SFDULabel sfdu = new SFDULabel(new String(sfduLabel).getBytes());
384     			if("CCSD".equals(sfdu.getControlAuthorityId())) {
385     				sfdus.add(sfdu);
386     				reader.read(sfduLabel);
387     				sfdus.add(new SFDULabel(new String(sfduLabel).getBytes()));
388     			}
389     		} catch (MalformedSFDULabel e) {
390     			//Ignore as there is likely not a header.
391     		}
392     	}
393     	return sfdus;
394     }
395     
396 	/***
397 	 * Determines whether or not a value is "good". A value is good
398 	 * when the number of curly braces, parenthesis, and quotes
399 	 * are equal. Braces and parenthesis located inside of quotes
400 	 * are ignored.
401 	 * 
402 	 * @param value
403 	 * @return true if the value is good.
404 	 * @throws BadValueException If
405 	 */
406 	private boolean checkValue(String value) throws BadValueException {
407 		boolean outsideQuotes = true;
408 		int braces = 0;
409 		int parenthesis = 0;
410 		
411 		for(int i = 0; i < value.length(); i++) {
412 			if(outsideQuotes) {
413 				if(value.charAt(i) == '{') 
414 					++braces;
415 				else if(value.charAt(i) == '}')
416 					--braces;
417 				else if(value.charAt(i) == '(')
418 					++parenthesis;
419 				else if(value.charAt(i) == ')')
420 					--parenthesis;
421 			}
422 			if(value.charAt(i) == '"')
423 				outsideQuotes = !outsideQuotes;
424 		}	
425 		if((!outsideQuotes) || (parenthesis != 0) || (braces !=0) )
426 			throw new BadValueException("Bad Value");
427 
428 		return true;
429 	}
430 }