1/*
2 * Copyright (c) 2003, 2007, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 *
23 */
24
25package sun.jvm.hotspot.utilities.soql;
26
27import java.util.*;
28import sun.jvm.hotspot.oops.*;
29import sun.jvm.hotspot.memory.*;
30import sun.jvm.hotspot.runtime.*;
31import sun.jvm.hotspot.utilities.*;
32
33/**
34 * This is SOQL (Simple Object Query Language) engine. This
35 * uses JavaScript engine for the "select" and "where" expression
36 * parts.
37 */
38public class SOQLEngine extends JSJavaScriptEngine {
39   public static synchronized SOQLEngine getEngine() {
40      if (soleInstance == null) {
41         soleInstance = new SOQLEngine();
42      }
43      return soleInstance;
44   }
45
46   /**
47      Query is of the form
48
49         select <java script code to select>
50         [ from [instanceof] <class name> [<identifier>]
51           [ where <java script boolean expression> ]
52         ]
53   */
54   public synchronized void executeQuery(String query, ObjectVisitor visitor)
55                                                 throws SOQLException {
56      debugPrint("query : " + query);
57      StringTokenizer st = new StringTokenizer(query);
58      if (st.hasMoreTokens()) {
59         String first = st.nextToken();
60         if (! first.equals("select") ) {
61            throw new SOQLException("query syntax error: no 'select' clause");
62         }
63      } else {
64         throw new SOQLException("query syntax error: no 'select' clause");
65      }
66
67      int selectStart = query.indexOf("select");
68      int fromStart = query.indexOf("from");
69
70      String selectExpr = null;
71      String className = null;
72      boolean isInstanceOf = false;
73      String whereExpr = null;
74      String identifier = null;
75
76      if (fromStart != -1) {
77         selectExpr = query.substring(selectStart + "select".length(), fromStart);
78         st = new StringTokenizer(query.substring(fromStart + "from".length()));
79
80         if (st.hasMoreTokens()) {
81            String tmp = st.nextToken();
82            if (tmp.equals("instanceof")) {
83               isInstanceOf = true;
84               if (! st.hasMoreTokens()) {
85                  throw new SOQLException("no class name after 'instanceof'");
86               }
87               className = st.nextToken();
88            } else {
89               className = tmp;
90            }
91         } else {
92            throw new SOQLException("query syntax error: class name must follow 'from'");
93         }
94
95         if (st.hasMoreTokens()) {
96            identifier = st.nextToken();
97            if (identifier.equals("where")) {
98               throw new SOQLException("query syntax error: identifier should follow class name");
99            }
100            if (st.hasMoreTokens()) {
101               String tmp = st.nextToken();
102               if (! tmp.equals("where")) {
103                  throw new SOQLException("query syntax error: 'where' clause expected after 'from' clause");
104               }
105               int whereEnd = query.lastIndexOf("where") + 5; // "where".length
106               whereExpr = query.substring(whereEnd);
107            }
108         } else {
109            throw new SOQLException("query syntax error: identifier should follow class name");
110         }
111      } else { // no from clause
112         selectExpr = query.substring(selectStart + "select".length(), query.length());
113      }
114
115      executeQuery(new SOQLQuery(selectExpr, isInstanceOf, className, identifier, whereExpr), visitor);
116   }
117
118   private void executeQuery(SOQLQuery q, ObjectVisitor visitor) throws SOQLException {
119      InstanceKlass kls = null;
120      if (q.className != null) {
121         kls = SystemDictionaryHelper.findInstanceKlass(q.className);
122         if (kls == null) {
123            throw new SOQLException(q.className + " is not found!");
124         }
125      }
126
127
128      StringBuffer buf = new StringBuffer();
129      buf.append("function result(");
130      if (q.identifier != null) {
131         buf.append(q.identifier);
132      }
133      buf.append(") { return ");
134      buf.append(q.selectExpr.replace('\n', ' '));
135      buf.append("; }");
136
137      String selectCode = buf.toString();
138      debugPrint(selectCode);
139      String whereCode = null;
140      if (q.whereExpr != null) {
141         buf = new StringBuffer();
142         buf.append("function filter(");
143         buf.append(q.identifier);
144         buf.append(") { return ");
145         buf.append(q.whereExpr.replace('\n', ' '));
146         buf.append("; }");
147         whereCode = buf.toString();
148         debugPrint(whereCode);
149      } else {
150         whereCode = "filter = null;";
151      }
152
153      beginQuery();
154      // compile select expression and where condition
155      evalString(selectCode, "", 1);
156      evalString(whereCode,  "", 1);
157
158      // iterate thru heap, if needed
159      if (q.className != null) {
160         try {
161            iterateOops(kls, visitor, q.isInstanceOf);
162         } finally {
163            endQuery();
164         }
165      } else {
166         // simple "select <expr>" query
167         try {
168            Object select = call("result", new Object[] {});
169            visitor.visit(select);
170         } catch (Exception e) {
171            e.printStackTrace();
172         }
173      }
174   }
175
176   private void dispatchObject(Oop oop, ObjectVisitor visitor, boolean filterExists) {
177      JSJavaObject jsObj = factory.newJSJavaObject(oop);
178      Object[] args = new Object[] { jsObj };
179      boolean b = true;
180
181      try {
182         if (filterExists) {
183            Object res = call("filter", args);
184            if (res instanceof Boolean) {
185               b = ((Boolean)res).booleanValue();
186            } else if (res instanceof Number) {
187               b = ((Number)res).intValue() != 0;
188            } else {
189               b = (res != null);
190            }
191         }
192
193         if (b) {
194            Object select = call("result", args);
195            visitor.visit(select);
196         }
197      } catch (Exception e) {
198         throw new RuntimeException(e);
199      }
200   }
201
202   private void iterateOops(final InstanceKlass ik, final ObjectVisitor visitor,
203                            boolean includeSubtypes) {
204      ObjectHeap oh = VM.getVM().getObjectHeap();
205      oh.iterateObjectsOfKlass(new HeapVisitor() {
206                    boolean filterExists;
207                    public void prologue(long usedSize) {
208                        filterExists = getScriptEngine().get("filter") != null;
209                    }
210                    public boolean doObj(Oop obj) {
211                       dispatchObject(obj, visitor, filterExists);
212                       return false;
213                    }
214                    public void epilogue() {}
215                 }, ik, includeSubtypes);
216   }
217
218   // we create fresh ObjectReader and factory to avoid
219   // excessive cache across queries.
220   private void beginQuery() {
221      objReader = new ObjectReader();
222      factory = new JSJavaFactoryImpl();
223   }
224
225   // at the end of query we clear object reader cache
226   // and factory cache
227   private void endQuery() {
228      objReader = null;
229      factory = null;
230   }
231
232   protected ObjectReader getObjectReader() {
233      return objReader;
234   }
235
236   protected JSJavaFactory getJSJavaFactory() {
237      return factory;
238   }
239
240   protected boolean isQuitting() {
241      return false;
242   }
243
244   protected void quit() {
245      // do nothing
246   }
247
248   private static void debugPrint(String msg) {
249      if (debug) System.out.println(msg);
250   }
251
252   private static final boolean debug;
253   static {
254      debug = System.getProperty("sun.jvm.hotspot.utilities.soql.SOQLEngine.debug") != null;
255   }
256
257   protected SOQLEngine() {
258       super(debug);
259       start();
260   }
261
262   private ObjectReader objReader;
263   private JSJavaFactory factory;
264   private static SOQLEngine soleInstance;
265}
266