1 package net.sf.sapjcosupport;
2
3 import org.apache.log4j.Logger;
4 import org.apache.oro.text.regex.*;
5
6 import java.util.*;
7
8 /**
9 * @author Niki Driessen
10 * @since Apr 21, 2006 - 10:47:03 AM
11 */
12 public class SapQuery {
13 private String searchHelpName, selectionMethod;
14 private int maximumResults;
15 private Class persistentObject;
16 private List javaFields = new ArrayList(2);
17 private List sapFields = new ArrayList(2);
18 private List criteria = new ArrayList(1);
19
20 protected final Logger log = Logger.getLogger(getClass());
21 private static Map optionMap;
22 private static final String operatorRegex = "(like|not like|==|=|<>|!=|<=|>=|<|>)";
23
24 static {
25 optionMap = new TreeMap();
26 optionMap.put("like", SearchCriterion.CONTAINS_PATTERN);
27 optionMap.put("not like", SearchCriterion.CONTAINS_NOT_PATTERN);
28 optionMap.put("==", SearchCriterion.EQUAL);
29 optionMap.put("=", SearchCriterion.EQUAL);
30 optionMap.put("<>", SearchCriterion.NOT_EQUAL);
31 optionMap.put("!=", SearchCriterion.NOT_EQUAL);
32 optionMap.put("<=", SearchCriterion.LESS_OR_EQUAL);
33 optionMap.put(">=", SearchCriterion.GREATER_OR_EQUAL);
34 optionMap.put("<", SearchCriterion.LESS);
35 optionMap.put(">", SearchCriterion.GREATER);
36
37
38
39 }
40
41 private static final String WHERE = "where";
42 private static final String FROM = "from";
43 private static final String SELECT = "select";
44
45 /**
46 * "partNumber = MATNR_EXT where MATNR_EXT like '?%' AND SPRAS = 'E'"
47 * "partNumber = MATNR_EXT, alternateDescription = MAKTG where MAKTG like '%?%' AND SPRAS = 'E'"
48 *
49 * @param query
50 */
51 public SapQuery(String query, Class persistentObject, int maximumResults) {
52 this.persistentObject = persistentObject;
53 this.maximumResults = maximumResults;
54 parseQuery(query);
55 }
56
57 public class QueryParseException extends RuntimeException {
58 public QueryParseException(String queryPart, String expected, int offset) {
59 super("Syntax error near: " + queryPart + ", at position: " + offset + ", expected: " + expected);
60 }
61 }
62
63 private class WhereClauseSplitter extends StringSplitter {
64 private Collection delims;
65
66 public WhereClauseSplitter(String string) {
67 super(string, "(and|or)", false);
68 }
69
70 public WhereClauseSplitter(String string, String regex) {
71 super(string, regex, false);
72 }
73
74 protected void split() {
75 try {
76 PatternCompiler compiler = new Perl5Compiler();
77 Pattern pattern;
78 if (caseSensitive) {
79 pattern = compiler.compile(delim);
80 } else {
81 pattern = compiler.compile(delim, Perl5Compiler.CASE_INSENSITIVE_MASK);
82 }
83 PatternMatcher partsMatcher = new Perl5Matcher();
84 parts = new ArrayList(5);
85 delims = new ArrayList(5);
86 int prev = 0;
87 PatternMatcherInput input = new PatternMatcherInput(string);
88 while (partsMatcher.contains(input, pattern)) {
89 MatchResult result = partsMatcher.getMatch();
90 parts.add(string.substring(prev, result.beginOffset(1)));
91 delims.add(result.group(1));
92 prev = result.endOffset(1) + 1;
93 input.setBeginOffset(prev);
94 }
95 parts.add(string.substring(prev));
96 } catch (Exception e) {
97 e.printStackTrace();
98 throw new QueryParseException(string, "valid condition expression", 0);
99 }
100 }
101
102 public Iterator iterator() {
103 return new WhereClauseIterator(parts.iterator(), delims.iterator());
104 }
105
106 public class WhereClauseIterator implements Iterator {
107 private String matchedDelim;
108 private Iterator internal;
109 private Iterator delims;
110
111 public WhereClauseIterator(Iterator parts, Iterator delims) {
112 this.internal = parts;
113 this.delims = delims;
114 }
115
116 public void remove() {
117 delims.remove();
118 internal.remove();
119 }
120
121 public boolean hasNext() {
122 return internal.hasNext();
123 }
124
125 public Object next() {
126 if (delims.hasNext()) {
127 matchedDelim = (String) delims.next();
128 }
129 return internal.next();
130 }
131
132 public String getMatchedDelim() {
133 return matchedDelim;
134 }
135 }
136
137 }
138
139 private class StringSplitter {
140 protected Collection parts;
141 protected String string;
142 protected String delim;
143 protected boolean caseSensitive;
144
145 /**
146 * Splits a string by delim.
147 *
148 * @param string
149 * @param delim
150 */
151 public StringSplitter(String string, String delim, boolean caseSensitive) {
152 this.string = string;
153 this.delim = delim;
154 this.caseSensitive = caseSensitive;
155 split();
156 }
157
158 protected void split() {
159 String source = this.string;
160 if (!caseSensitive) {
161 source = string.toLowerCase();
162 delim = delim.toLowerCase();
163 }
164 parts = new ArrayList();
165 int prev = 0;
166 int index = source.indexOf(delim);
167 while (index > 0) {
168 String part = string.substring(prev, index);
169 parts.add(part.trim());
170 prev = index + delim.length() + 1;
171 index = source.indexOf(delim, prev);
172 }
173
174 parts.add(string.substring(prev).trim());
175 }
176
177 public Iterator iterator() {
178 return parts.iterator();
179 }
180
181 public int countParts() {
182 return parts.size();
183 }
184 }
185
186 public void parseQuery(String query) {
187
188 StringSplitter whereSplitter = new StringSplitter(query, WHERE, false);
189 if (whereSplitter.countParts() != 2) {
190 throw new QueryParseException(query, WHERE, query.length());
191 }
192 Iterator mainParts = whereSplitter.iterator();
193 String temp = (String) mainParts.next();
194 StringSplitter selectFromSplitter = new StringSplitter(temp, FROM, false);
195 if (selectFromSplitter.countParts() != 2) {
196 throw new QueryParseException(temp, FROM, query.length());
197 }
198 Iterator selectFromParts = selectFromSplitter.iterator();
199 String selectClause = (String) selectFromParts.next();
200 selectClause = replaceFirst(selectClause, SELECT, "");
201 String fromClause = (String) selectFromParts.next();
202 String whereClause = (String) mainParts.next();
203
204 StringTokenizer fieldSelectionTokenizer = new StringTokenizer(selectClause, ",");
205 while (fieldSelectionTokenizer.hasMoreTokens()) {
206 String part = fieldSelectionTokenizer.nextToken().trim();
207 parseFieldSelection(part, query.indexOf(part));
208 }
209
210
211 StringTokenizer fromTokenizer = new StringTokenizer(fromClause, ".");
212 if (fromTokenizer.countTokens() != 2) {
213 throw new QueryParseException(fromClause, "from SEARCHHELP.SELECTION_METHOD", fromClause.length());
214 }
215 searchHelpName = fromTokenizer.nextToken();
216 selectionMethod = fromTokenizer.nextToken();
217
218 WhereClauseSplitter.WhereClauseIterator whereParts = (WhereClauseSplitter.WhereClauseIterator)
219 new WhereClauseSplitter(whereClause).iterator();
220 while (whereParts.hasNext()) {
221 String part = (String) whereParts.next();
222 String delim = whereParts.getMatchedDelim();
223 parseCriteria(part);
224 }
225 }
226
227 private String replaceFirst(String haystack, String needle, String replacement) {
228 int pos = haystack.indexOf(needle);
229 if (pos >= 0) {
230 return (pos > 0 ? haystack.substring(0, pos - 1) : "") +
231 replacement +
232 (((pos + needle.length()) < haystack.length()) ?
233 haystack.substring(pos + needle.length()) : "");
234 }
235 return haystack;
236 }
237
238 /**
239 * Parses expression of the form 'javaField = SAP_FIELD'.
240 * spaces are optional (can be none or more between the parts of the expression)
241 *
242 * @param queryPart the part of the query to interprete as a field selection
243 */
244 private void parseFieldSelection(String queryPart, int offset) {
245 StringTokenizer tokenizer = new StringTokenizer(queryPart, "=");
246 if (tokenizer.countTokens() != 2) {
247 throw new QueryParseException(queryPart, "=", offset);
248 }
249 String javaField = tokenizer.nextToken().trim();
250 String sapField = tokenizer.nextToken().trim();
251 javaFields.add(javaField);
252 sapFields.add(sapField);
253 }
254
255 private void parseCriteria(String queryPart) {
256 WhereClauseSplitter splitter = new WhereClauseSplitter(queryPart, operatorRegex);
257 WhereClauseSplitter.WhereClauseIterator clauseParts = (WhereClauseSplitter.WhereClauseIterator) splitter.iterator();
258 if (splitter.countParts() != 2) {
259 throw new QueryParseException(queryPart, "'field [operator] value'", 0);
260 } else {
261 String field = ((String) clauseParts.next()).trim();
262 String option = (String) optionMap.get(clauseParts.getMatchedDelim());
263 String lowLimit = ((String) clauseParts.next()).trim();
264 if (field.length() == 0) {
265 throw new QueryParseException(queryPart, "valid field name before operator", 0);
266 }
267 if (option == null) {
268 throw new QueryParseException(queryPart, "valid operator", 0);
269 }
270 if (lowLimit.length() == 0) {
271 throw new QueryParseException(queryPart, "valid expression after operator", 0);
272 }
273 if (lowLimit.charAt(0) == '\'') {
274 lowLimit = lowLimit.substring(1);
275 }
276 if (lowLimit.charAt(lowLimit.length() - 1) == '\'') {
277 lowLimit = lowLimit.substring(0, lowLimit.length() - 1);
278 }
279 lowLimit = lowLimit.replace('%', '*');
280 SearchCriterion criterion = new SearchCriterion();
281 criterion.setField(field);
282
283 criterion.setLowLimit(lowLimit);
284 criterion.setIncluded(true);
285 criterion.setOption(option);
286 criteria.add(criterion);
287 }
288 }
289
290 public void setParameter(String parameter) {
291 setParameter(0, parameter);
292 }
293
294 public void setParameter(int position, String parameter) {
295 SearchCriterion criterion = (SearchCriterion) criteria.get(position);
296 if (criterion != null) {
297 String lowLimit = criterion.getLowLimit();
298 lowLimit = replaceFirst(lowLimit, "?", parameter);
299 criterion.setLowLimit(lowLimit);
300 } else {
301 throw new IndexOutOfBoundsException("No parameter at position " + position);
302 }
303 }
304
305 public void setParameters(String[] params) {
306 for (int i = 0; i < params.length; i++) {
307 String param = params[i];
308 setParameter(i, param);
309 }
310 }
311
312 public List executeQuery(SapSearchHelp searchHelper) {
313 return searchHelper.search(searchHelpName, selectionMethod, maximumResults, criteria, persistentObject, javaFields, sapFields);
314 }
315 }