View Javadoc

1   package org.springframework.roo.shell;
2   
3   import java.util.LinkedHashMap;
4   import java.util.Map;
5   
6   import org.springframework.roo.support.util.Assert;
7   
8   public class ParserUtils {
9   	
10  	/**
11  	 * Converts a particular buffer into a tokenized structure.
12  	 * 
13  	 * Properly treats double quotes (") as option delimiters.
14  	 * 
15  	 * Expects option names to be preceded by a single or double dash. We call this an "option marker".
16  	 * 
17  	 * Treats spaces as the default option tokenizer.
18  	 * 
19  	 * Any token without an option marker is considered the default. The default is returned in the Map as an
20  	 * element with an empty string key (""). There can only be a single default.
21  	 * 
22  	 * @param remainingBuffer to tokenize
23  	 * @return a Map where keys are the option names (minus any dashes) and values are the option values (any double-quotes are removed)
24  	 * 
25  	 */
26  	public static Map<String,String> tokenize(String remainingBuffer) {
27  		Assert.notNull(remainingBuffer, "Remaining buffer cannot be null, although it can be empty");
28  		Map<String,String> result = new LinkedHashMap<String, String>();
29  		StringBuilder currentOption = new StringBuilder();
30  		StringBuilder currentValue = new StringBuilder();
31  		boolean inQuotes = false;
32  		
33  		// Verify correct number of double quotes are present
34  		int count = 0;
35  		for (char c : remainingBuffer.toCharArray()) {
36  			if ('"' == c) {
37  				count++;
38  			}
39  		}
40  		Assert.isTrue(count % 2 == 0, "Cannot have an unbalanced number of quotation marks");
41  		
42  		if ("".equals(remainingBuffer.trim())) {
43  			// They've not specified anything, so exit now
44  			return result;
45  		}
46  		
47  		String[] split = remainingBuffer.split(" ");
48  		for (int i = 0 ; i < split.length; i++) {
49  			String currentToken = split[i];
50  			
51  			if (inQuotes == true) {
52  				// We're only interested in this token series ending
53  				if (currentToken.endsWith("\"")) {
54  					// The current token series has ended
55  					String tokenLessDelimiters = currentToken.substring(0, currentToken.length()-1);
56  					currentValue.append(" ").append(tokenLessDelimiters);
57  					inQuotes = false;
58  					
59  					// Store this now-ended token series
60  					store(result, currentOption, currentValue);
61  					currentOption = new StringBuilder();
62  					currentValue = new StringBuilder();
63  					
64  				} else {
65  					// The current token series has not ended
66  					currentValue.append(" ").append(currentToken);
67  				}
68  				continue;
69  			}
70  			
71  			if (currentToken.startsWith("\"")) {
72  				// We're about to start a new delimited token
73  				String tokenLessDelimiters = currentToken.substring(1);
74  				currentValue.append(tokenLessDelimiters);
75  				inQuotes = true;
76  				continue;
77  			}
78  			
79  			if (currentToken.trim().equals("")) {
80  				// It's simply empty, so ignore it (ROO-23)
81  				continue;
82  			}
83  			
84  			if (currentToken.startsWith("--")) {
85  				// We're about to start a new option marker
86  				// First strip of the - or -- or however many there are
87  				int lastIndex = currentToken.lastIndexOf("-");
88  				String tokenLessDelimiters = currentToken.substring(lastIndex+1);
89  				currentOption.append(tokenLessDelimiters);
90  				
91  				// Store this token if it's the last one, or the next token starts with a "-"
92  				if (i+1 == split.length) {
93  					// We're at the end of the tokens, so store this one and stop processing
94  					store(result, currentOption, currentValue);
95  					break;
96  				}
97  				
98  				if (split[i+1].startsWith("-")) {
99  					// A new token is being started next iteration, so store this one now
100 					store(result, currentOption, currentValue);
101 					currentOption = new StringBuilder();
102 					currentValue = new StringBuilder();
103 				}
104 				
105 				continue;
106 			}
107 			
108 			// We must be in a standard token
109 			
110 			// If the standard token has no option name, we allow it to contain unquoted spaces
111 			if (currentOption.length() == 0) {
112 				if (currentValue.length() > 0) {
113 					// Existing content, so add a space first
114 					currentValue.append(" ");
115 				}
116 				currentValue.append(currentToken);
117 				
118 				// Store this token if it's the last one, or the next token starts with a "-"
119 				if (i+1 == split.length) {
120 					// We're at the end of the tokens, so store this one and stop processing
121 					store(result, currentOption, currentValue);
122 					break;
123 				}
124 				
125 				if (split[i+1].startsWith("--")) {
126 					// A new token is being started next iteration, so store this one now
127 					store(result, currentOption, currentValue);
128 					currentOption = new StringBuilder();
129 					currentValue = new StringBuilder();
130 				}
131 				
132 				continue;
133 			}
134 
135 			// This is an ordinary token, so store it now
136 			currentValue.append(currentToken);
137 			store(result, currentOption, currentValue);
138 			currentOption = new StringBuilder();
139 			currentValue = new StringBuilder();
140 		}
141 		
142 		// Strip out an empty default option, if it was returned (ROO-379)
143 		if (result.containsKey("") && result.get("").trim().equals("")) {
144 			result.remove("");
145 		}
146 
147 		return result;
148 	}
149 	
150 	private static void store(Map<String,String> results, StringBuilder currentOption, StringBuilder currentValue) {
151 		
152 		if (currentOption.length() > 0) {
153 			// There is an option marker
154 			String option = currentOption.toString();
155 			Assert.isTrue(!results.containsKey(option), "You cannot specify option '" + option + "' more than once in a single command");
156 			results.put(option, currentValue.toString());
157 		} else {
158 			// There was no option marker, so verify this isn't the first
159 			Assert.isTrue(!results.containsKey(""), "You cannot add more than one default option ('" + currentValue.toString() + "') in a single command");
160 			results.put("", currentValue.toString());
161 		}
162 	}
163 
164 }