1 /*
   2  * Copyright (c) 1997, 2008, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 
  27 /*
  28  * The Original Code is HAT. The Initial Developer of the
  29  * Original Code is Bill Foote, with contributions from others
  30  * at JavaSoft/Sun.
  31  */
  32 
  33 package com.sun.tools.hat.internal.oql;
  34 
  35 import com.sun.tools.hat.internal.model.*;
  36 import java.io.*;
  37 import java.lang.reflect.*;
  38 import java.util.*;
  39 
  40 /**
  41  * This is Object Query Language Interpreter
  42  *
  43  */
  44 public class OQLEngine {
  45     static {
  46         try {
  47             // Do we have javax.script support?
  48             // create ScriptEngineManager
  49             Class<?> managerClass = Class.forName("javax.script.ScriptEngineManager");
  50             Object manager = managerClass.newInstance();
  51 
  52             // create JavaScript engine
  53             Method getEngineMethod = managerClass.getMethod("getEngineByName",
  54                                 new Class<?>[] { String.class });
  55             Object jse = getEngineMethod.invoke(manager, new Object[] {"js"});
  56             oqlSupported = (jse != null);
  57         } catch (Exception exp) {
  58             oqlSupported = false;
  59         }
  60     }
  61 
  62     // check OQL is supported or not before creating OQLEngine
  63     public static boolean isOQLSupported() {
  64         return oqlSupported;
  65     }
  66 
  67     public OQLEngine(Snapshot snapshot) {
  68         if (!isOQLSupported()) {
  69             throw new UnsupportedOperationException("OQL not supported");
  70         }
  71         init(snapshot);
  72     }
  73 
  74     /**
  75        Query is of the form
  76 
  77           select &lt;java script code to select&gt;
  78           [ from [instanceof] &lt;class name&gt; [&lt;identifier&gt;]
  79             [ where &lt;java script boolean expression&gt; ]
  80           ]
  81     */
  82     public synchronized void executeQuery(String query, ObjectVisitor visitor)
  83                                           throws OQLException {
  84         debugPrint("query : " + query);
  85         StringTokenizer st = new StringTokenizer(query);
  86         if (st.hasMoreTokens()) {
  87             String first = st.nextToken();
  88             if (! first.equals("select") ) {
  89                 // Query does not start with 'select' keyword.
  90                 // Just treat it as plain JavaScript and eval it.
  91                 try {
  92                     Object res = evalScript(query);
  93                     visitor.visit(res);
  94                 } catch (Exception e) {
  95                     throw new OQLException(e);
  96                 }
  97                 return;
  98             }
  99         } else {
 100             throw new OQLException("query syntax error: no 'select' clause");
 101         }
 102 
 103         String selectExpr = "";
 104         boolean seenFrom = false;
 105         while (st.hasMoreTokens()) {
 106             String tok = st.nextToken();
 107             if (tok.equals("from")) {
 108                 seenFrom = true;
 109                 break;
 110             }
 111             selectExpr += " " + tok;
 112         }
 113 
 114         if (selectExpr.equals("")) {
 115             throw new OQLException("query syntax error: 'select' expression can not be empty");
 116         }
 117 
 118         String className = null;
 119         boolean isInstanceOf = false;
 120         String whereExpr =  null;
 121         String identifier = null;
 122 
 123         if (seenFrom) {
 124             if (st.hasMoreTokens()) {
 125                 String tmp = st.nextToken();
 126                 if (tmp.equals("instanceof")) {
 127                     isInstanceOf = true;
 128                     if (! st.hasMoreTokens()) {
 129                         throw new OQLException("no class name after 'instanceof'");
 130                     }
 131                     className = st.nextToken();
 132                 } else {
 133                     className = tmp;
 134                 }
 135             } else {
 136                 throw new OQLException("query syntax error: class name must follow 'from'");
 137             }
 138 
 139             if (st.hasMoreTokens()) {
 140                 identifier = st.nextToken();
 141                 if (identifier.equals("where")) {
 142                     throw new OQLException("query syntax error: identifier should follow class name");
 143                 }
 144                 if (st.hasMoreTokens()) {
 145                     String tmp = st.nextToken();
 146                     if (! tmp.equals("where")) {
 147                         throw new OQLException("query syntax error: 'where' clause expected after 'from' clause");
 148                     }
 149 
 150                     whereExpr = "";
 151                     while (st.hasMoreTokens()) {
 152                         whereExpr += " " + st.nextToken();
 153                     }
 154                     if (whereExpr.equals("")) {
 155                         throw new OQLException("query syntax error: 'where' clause cannot have empty expression");
 156                     }
 157                 }
 158             } else {
 159                 throw new OQLException("query syntax error: identifier should follow class name");
 160             }
 161         }
 162 
 163         executeQuery(new OQLQuery(selectExpr, isInstanceOf, className,
 164                                   identifier, whereExpr), visitor);
 165     }
 166 
 167     private void executeQuery(OQLQuery q, ObjectVisitor visitor)
 168                               throws OQLException {
 169         JavaClass clazz = null;
 170         if (q.className != null) {
 171             clazz = snapshot.findClass(q.className);
 172             if (clazz == null) {
 173                 throw new OQLException(q.className + " is not found!");
 174             }
 175         }
 176 
 177         StringBuffer buf = new StringBuffer();
 178         buf.append("function __select__(");
 179         if (q.identifier != null) {
 180             buf.append(q.identifier);
 181         }
 182         buf.append(") { return ");
 183         buf.append(q.selectExpr.replace('\n', ' '));
 184         buf.append("; }");
 185 
 186         String selectCode = buf.toString();
 187         debugPrint(selectCode);
 188         String whereCode = null;
 189         if (q.whereExpr != null) {
 190             buf = new StringBuffer();
 191             buf.append("function __where__(");
 192             buf.append(q.identifier);
 193             buf.append(") { return ");
 194             buf.append(q.whereExpr.replace('\n', ' '));
 195             buf.append("; }");
 196             whereCode = buf.toString();
 197         }
 198         debugPrint(whereCode);
 199 
 200         // compile select expression and where condition
 201         try {
 202             evalMethod.invoke(engine, new Object[] { selectCode });
 203             if (whereCode != null) {
 204                 evalMethod.invoke(engine, new Object[] { whereCode });
 205             }
 206 
 207             if (q.className != null) {
 208                 Enumeration<JavaHeapObject> objects = clazz.getInstances(q.isInstanceOf);
 209                 while (objects.hasMoreElements()) {
 210                     JavaHeapObject obj = objects.nextElement();
 211                     Object[] args = new Object[] { wrapJavaObject(obj) };
 212                     boolean b = (whereCode == null);
 213                     if (!b) {
 214                         Object res = call("__where__", args);
 215                         if (res instanceof Boolean) {
 216                             b = ((Boolean)res).booleanValue();
 217                         } else if (res instanceof Number) {
 218                             b = ((Number)res).intValue() != 0;
 219                         } else {
 220                             b = (res != null);
 221                         }
 222                     }
 223 
 224                     if (b) {
 225                         Object select = call("__select__", args);
 226                         if (visitor.visit(select)) return;
 227                     }
 228                 }
 229             } else {
 230                 // simple "select <expr>" query
 231                 Object select = call("__select__", new Object[] {});
 232                 visitor.visit(select);
 233             }
 234         } catch (Exception e) {
 235             throw new OQLException(e);
 236         }
 237     }
 238 
 239     public Object evalScript(String script) throws Exception {
 240         return evalMethod.invoke(engine, new Object[] { script });
 241     }
 242 
 243     public Object wrapJavaObject(JavaHeapObject obj) throws Exception {
 244         return call("wrapJavaObject", new Object[] { obj });
 245     }
 246 
 247     public Object toHtml(Object obj) throws Exception {
 248         return call("toHtml", new Object[] { obj });
 249     }
 250 
 251     public Object call(String func, Object[] args) throws Exception {
 252         return invokeMethod.invoke(engine, new Object[] { func, args });
 253     }
 254 
 255     private static void debugPrint(String msg) {
 256         if (debug) System.out.println(msg);
 257     }
 258 
 259     private void init(Snapshot snapshot) throws RuntimeException {
 260         this.snapshot = snapshot;
 261         try {
 262             // create ScriptEngineManager
 263             Class<?> managerClass = Class.forName("javax.script.ScriptEngineManager");
 264             Object manager = managerClass.newInstance();
 265 
 266             // create JavaScript engine
 267             Method getEngineMethod = managerClass.getMethod("getEngineByName",
 268                                 new Class<?>[] { String.class });
 269             engine = getEngineMethod.invoke(manager, new Object[] {"js"});
 270 
 271             // initialize engine with init file (hat.js)
 272             InputStream strm = getInitStream();
 273             Class<?> engineClass = Class.forName("javax.script.ScriptEngine");
 274             evalMethod = engineClass.getMethod("eval",
 275                                 new Class<?>[] { Reader.class });
 276             evalMethod.invoke(engine, new Object[] {new InputStreamReader(strm)});
 277 
 278             // initialize ScriptEngine.eval(String) and
 279             // Invocable.invokeFunction(String, Object[]) methods.
 280             Class<?> invocableClass = Class.forName("javax.script.Invocable");
 281 
 282             evalMethod = engineClass.getMethod("eval",
 283                                   new Class<?>[] { String.class });
 284             invokeMethod = invocableClass.getMethod("invokeFunction",
 285                                   new Class<?>[] { String.class, Object[].class });
 286 
 287             // initialize ScriptEngine.put(String, Object) method
 288             Method putMethod = engineClass.getMethod("put",
 289                                   new Class<?>[] { String.class, Object.class });
 290 
 291             // call ScriptEngine.put to initialize built-in heap object
 292             putMethod.invoke(engine, new Object[] {
 293                         "heap", call("wrapHeapSnapshot", new Object[] { snapshot })
 294                     });
 295         } catch (Exception e) {
 296             if (debug) e.printStackTrace();
 297             throw new RuntimeException(e);
 298         }
 299     }
 300 
 301     private InputStream getInitStream() {
 302         return getClass().getResourceAsStream("/com/sun/tools/hat/resources/hat.js");
 303     }
 304 
 305     private Object engine;
 306     private Method evalMethod;
 307     private Method invokeMethod;
 308     private Snapshot snapshot;
 309     private static boolean debug = false;
 310     private static boolean oqlSupported;
 311 }