[UI] Allow logical plan to be viewed as JSON / formatted JSON

- user model changes: no
- storage format changes: no
- interface changes: enhancements to the web interface

details:
Added drop-down menu for printing logical plan and optimized
logical plan in string,json, and clean-json.

Change-Id: I4dd62e355048a5b8a02e074049fe41e73e74e357
Reviewed-on: https://asterix-gerrit.ics.uci.edu/1885
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Contrib: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Till Westmann <tillw@apache.org>
Reviewed-by: Ian Maxon <imaxon@apache.org>
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/SessionConfig.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/SessionConfig.java
index cfd4e87..6e67612 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/SessionConfig.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/SessionConfig.java
@@ -21,6 +21,9 @@
 import java.io.Serializable;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
 
 /**
  * SessionConfig captures several different parameters for controlling
@@ -36,7 +39,6 @@
  * <li>It allows you to specify output format-specific parameters.
  */
 public class SessionConfig implements Serializable {
-
     private static final long serialVersionUID = 1L;
 
     /**
@@ -50,6 +52,29 @@
     };
 
     /**
+     * Used to specify the format for logical plan and optimized logical plan.
+     */
+
+    public enum PlanFormat {
+        JSON,
+        STRING;
+        public static PlanFormat get(String fmtString, String label, PlanFormat defaultFmt, Logger logger) {
+            try {
+                if (fmtString != null) {
+                    String format =
+                            ("JSON".equalsIgnoreCase(fmtString) || "CLEAN_JSON".equalsIgnoreCase(fmtString))
+                                    ? "JSON"
+                                    : fmtString;
+                    return PlanFormat.valueOf(format);
+                }
+            } catch (IllegalArgumentException e) {
+                logger.log(Level.INFO, fmtString + ": unsupported " + label + ", using " + defaultFmt + "instead", e);
+            }
+            return defaultFmt;
+        }
+    };
+
+    /**
      * Produce out-of-band output for Hyracks Job.
      */
     public static final String OOB_HYRACKS_JOB = "oob-hyracks-job";
@@ -106,6 +131,7 @@
 
     // Output format.
     private final OutputFormat fmt;
+    private final PlanFormat lpfmt;
 
     // Standard execution flags.
     private final boolean executeQuery;
@@ -116,13 +142,18 @@
     private final Map<String, Boolean> flags;
 
     public SessionConfig(OutputFormat fmt) {
-        this(fmt, true, true, true);
+        this(fmt, PlanFormat.STRING);
+    }
+
+    public SessionConfig(OutputFormat fmt, PlanFormat lpfmt) {
+        this(fmt, true, true, true, lpfmt);
     }
 
     /**
      * Create a SessionConfig object with all optional values set to defaults:
      * - All format flags set to "false".
      * - All out-of-band outputs set to "false".
+     *
      * @param fmt
      *            Output format for execution output.
      * @param optimize
@@ -131,13 +162,20 @@
      *            Whether to execute the query or not.
      * @param generateJobSpec
      *            Whether to generate the Hyracks job specification (if
+     * @param lpfmt
+     *            Plan format for logical plan.
      */
     public SessionConfig(OutputFormat fmt, boolean optimize, boolean executeQuery, boolean generateJobSpec) {
+        this(fmt, optimize, executeQuery, generateJobSpec, PlanFormat.STRING);
+    }
+    public SessionConfig(OutputFormat fmt, boolean optimize, boolean executeQuery, boolean generateJobSpec,
+            PlanFormat lpfmt) {
         this.fmt = fmt;
         this.optimize = optimize;
         this.executeQuery = executeQuery;
         this.generateJobSpec = generateJobSpec;
         this.flags = new HashMap<>();
+        this.lpfmt = lpfmt;
     }
 
     /**
@@ -148,6 +186,13 @@
     }
 
     /**
+     * Retrieve the PlanFormat for this execution.
+     */
+    public PlanFormat getLpfmt() {
+        return this.lpfmt;
+    }
+
+    /**
      * Retrieve the value of the "execute query" flag.
      */
     public boolean isExecuteQuery() {
diff --git a/asterixdb/asterix-app/pom.xml b/asterixdb/asterix-app/pom.xml
index ee733d9..b039071 100644
--- a/asterixdb/asterix-app/pom.xml
+++ b/asterixdb/asterix-app/pom.xml
@@ -279,6 +279,10 @@
   </profiles>
   <dependencies>
     <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-databind</artifactId>
+    </dependency>
+    <dependency>
       <groupId>org.apache.hyracks</groupId>
       <artifactId>hyracks-control-cc</artifactId>
     </dependency>
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java
index 567d587..8290446 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/common/APIFramework.java
@@ -84,8 +84,10 @@
 import org.apache.hyracks.algebricks.core.algebra.expressions.IExpressionTypeComputer;
 import org.apache.hyracks.algebricks.core.algebra.expressions.IMergeAggregationExpressionFactory;
 import org.apache.hyracks.algebricks.core.algebra.expressions.IMissableTypeComputer;
+import org.apache.hyracks.algebricks.core.algebra.prettyprint.AbstractLogicalOperatorPrettyPrintVisitor;
 import org.apache.hyracks.algebricks.core.algebra.prettyprint.AlgebricksAppendable;
 import org.apache.hyracks.algebricks.core.algebra.prettyprint.LogicalOperatorPrettyPrintVisitor;
+import org.apache.hyracks.algebricks.core.algebra.prettyprint.LogicalOperatorPrettyPrintVisitorJson;
 import org.apache.hyracks.algebricks.core.algebra.prettyprint.PlanPrettyPrinter;
 import org.apache.hyracks.algebricks.core.rewriter.base.AlgebricksOptimizationContext;
 import org.apache.hyracks.algebricks.core.rewriter.base.IOptimizationContextFactory;
@@ -111,6 +113,8 @@
     private static final int MIN_FRAME_LIMIT_FOR_SORT = 3;
     private static final int MIN_FRAME_LIMIT_FOR_GROUP_BY = 4;
     private static final int MIN_FRAME_LIMIT_FOR_JOIN = 5;
+    private static final String LPLAN = "Logical plan";
+    private static final String OPLAN = "Optimized logical plan";
 
     // A white list of supported configurable parameters.
     private static final Set<String> CONFIGURABLE_PARAMETER_NAMES =
@@ -156,7 +160,13 @@
     private void printPlanPrefix(SessionOutput output, String planName) {
         if (output.config().is(SessionConfig.FORMAT_HTML)) {
             output.out().println("<h4>" + planName + ":</h4>");
-            output.out().println("<pre>");
+            if (LPLAN.equalsIgnoreCase(planName)) {
+                output.out().println("<pre class = query-plan>");
+            } else if (OPLAN.equalsIgnoreCase(planName)) {
+                output.out().println("<pre class = query-optimized-plan>");
+            } else {
+                output.out().println("<pre>");
+            }
         } else {
             output.out().println("----------" + planName + ":");
         }
@@ -219,7 +229,13 @@
 
             printPlanPrefix(output, "Logical plan");
             if (rwQ != null || (statement != null && statement.getKind() == Statement.Kind.LOAD)) {
-                LogicalOperatorPrettyPrintVisitor pvisitor = new LogicalOperatorPrettyPrintVisitor(output.out());
+                AbstractLogicalOperatorPrettyPrintVisitor pvisitor;
+                if (output.config().getLpfmt().equals(SessionConfig.PlanFormat.JSON)) {
+                    pvisitor = new LogicalOperatorPrettyPrintVisitorJson(output.out());
+                } else {
+                    pvisitor = new LogicalOperatorPrettyPrintVisitor(output.out());
+
+                }
                 PlanPrettyPrinter.printPlan(plan, pvisitor, 0);
             }
             printPlanPostfix(output);
@@ -273,8 +289,13 @@
                 } else {
                     printPlanPrefix(output, "Optimized logical plan");
                     if (rwQ != null || (statement != null && statement.getKind() == Statement.Kind.LOAD)) {
-                        LogicalOperatorPrettyPrintVisitor pvisitor =
-                                new LogicalOperatorPrettyPrintVisitor(output.out());
+                        AbstractLogicalOperatorPrettyPrintVisitor pvisitor;
+                        if (output.config().getLpfmt().equals(SessionConfig.PlanFormat.JSON)) {
+                            pvisitor = new LogicalOperatorPrettyPrintVisitorJson(output.out());
+
+                        } else {
+                            pvisitor = new LogicalOperatorPrettyPrintVisitor(output.out());
+                        }
                         PlanPrettyPrinter.printPlan(plan, pvisitor, 0);
                     }
                     printPlanPostfix(output);
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ApiServlet.java
index fbe5852..a29d869 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/ApiServlet.java
@@ -52,6 +52,7 @@
 import org.apache.asterix.translator.IStatementExecutorFactory;
 import org.apache.asterix.translator.SessionConfig;
 import org.apache.asterix.translator.SessionConfig.OutputFormat;
+import org.apache.asterix.translator.SessionConfig.PlanFormat;
 import org.apache.asterix.translator.SessionOutput;
 import org.apache.hyracks.api.client.IHyracksClientConnection;
 import org.apache.hyracks.api.dataset.IHyracksDataset;
@@ -67,7 +68,6 @@
 import io.netty.handler.codec.http.HttpResponseStatus;
 
 public class ApiServlet extends AbstractServlet {
-
     private static final Logger LOGGER = Logger.getLogger(ApiServlet.class.getName());
     public static final String HTML_STATEMENT_SEPARATOR = "<!-- BEGIN -->";
 
@@ -88,18 +88,20 @@
         this.componentProvider = componentProvider;
     }
 
-    @Override
-    protected void post(IServletRequest request, IServletResponse response) {
+    @Override protected void post(IServletRequest request, IServletResponse response) {
         // Query language
-        ILangCompilationProvider compilationProvider = "AQL".equals(request.getParameter("query-language"))
-                ? aqlCompilationProvider : sqlppCompilationProvider;
+        ILangCompilationProvider compilationProvider = "AQL".equals(request.getParameter("query-language")) ?
+                aqlCompilationProvider :
+                sqlppCompilationProvider;
         IParserFactory parserFactory = compilationProvider.getParserFactory();
 
         // Output format.
         PrintWriter out = response.writer();
         OutputFormat format;
+
         boolean csvAndHeader = false;
         String output = request.getParameter("output-format");
+
         if ("CSV-Header".equals(output)) {
             output = "CSV";
             csvAndHeader = true;
@@ -112,6 +114,8 @@
             // Default output format
             format = OutputFormat.CLEAN_JSON;
         }
+        PlanFormat planFormat =
+                PlanFormat.get(request.getParameter("plan-format"), "plan format", PlanFormat.STRING, LOGGER);
 
         String query = request.getParameter("query");
         String wrapperArray = request.getParameter("wrapper-array");
@@ -144,12 +148,14 @@
             }
             IParser parser = parserFactory.createParser(query);
             List<Statement> aqlStatements = parser.parse();
-            SessionConfig sessionConfig = new SessionConfig(format, true, isSet(executeQuery), true);
+            SessionConfig sessionConfig =
+                    new SessionConfig(format, true, isSet(executeQuery), true, planFormat);
             sessionConfig.set(SessionConfig.FORMAT_HTML, true);
             sessionConfig.set(SessionConfig.FORMAT_CSV_HEADER, csvAndHeader);
             sessionConfig.set(SessionConfig.FORMAT_WRAPPER_ARRAY, isSet(wrapperArray));
-            sessionConfig.setOOBData(isSet(printExprParam), isSet(printRewrittenExprParam),
-                    isSet(printLogicalPlanParam), isSet(printOptimizedLogicalPlanParam), isSet(printJob));
+            sessionConfig
+                    .setOOBData(isSet(printExprParam), isSet(printRewrittenExprParam), isSet(printLogicalPlanParam),
+                            isSet(printOptimizedLogicalPlanParam), isSet(printJob));
             SessionOutput sessionOutput = new SessionOutput(sessionConfig, out);
             MetadataManager.INSTANCE.init();
             IStatementExecutor translator = statementExectorFactory.create(appCtx, aqlStatements, sessionOutput,
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
index 6a92a26..f8f5c18 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/QueryServiceServlet.java
@@ -114,7 +114,8 @@
         CLIENT_ID("client_context_id"),
         PRETTY("pretty"),
         MODE("mode"),
-        TIMEOUT("timeout");
+        TIMEOUT("timeout"),
+        PLAN_FORMAT("plan-format");
 
         private final String str;
 
@@ -236,6 +237,7 @@
         SessionOutput.ResultAppender appendStatus = ResultUtil.createResultStatusAppender();
 
         SessionConfig.OutputFormat format = getFormat(param.format);
+        //TODO:get the parameters from UI.Currently set to clean_json.
         SessionConfig sessionConfig = new SessionConfig(format);
         sessionConfig.set(SessionConfig.FORMAT_WRAPPER_ARRAY, true);
         sessionConfig.set(SessionConfig.FORMAT_INDENT_JSON, param.pretty);
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/RestApiServlet.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/RestApiServlet.java
index 3d00d88..117d7fb 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/RestApiServlet.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/http/server/RestApiServlet.java
@@ -46,6 +46,7 @@
 import org.apache.asterix.translator.IStatementExecutorFactory;
 import org.apache.asterix.translator.SessionConfig;
 import org.apache.asterix.translator.SessionConfig.OutputFormat;
+import org.apache.asterix.translator.SessionConfig.PlanFormat;
 import org.apache.asterix.translator.SessionOutput;
 import org.apache.hyracks.api.client.IHyracksClientConnection;
 import org.apache.hyracks.api.dataset.IHyracksDataset;
@@ -107,6 +108,8 @@
                 format = OutputFormat.CSV;
             }
         }
+        PlanFormat planFormat =
+                PlanFormat.get(request.getParameter("plan-format"), "plan format", PlanFormat.STRING, LOGGER);
 
         // If it's JSON, check for the "lossless" flag
 
@@ -117,7 +120,7 @@
 
         SessionOutput.ResultAppender appendHandle = (app, handle) -> app.append("{ \"").append("handle")
                 .append("\":" + " \"").append(handle).append("\" }");
-        SessionConfig sessionConfig = new SessionConfig(format);
+        SessionConfig sessionConfig = new SessionConfig(format, planFormat);
 
         // If it's JSON or ADM, check for the "wrapper-array" flag. Default is
         // "true" for JSON and "false" for ADM. (Not applicable for CSV.)
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/java/AsterixJavaClient.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/java/AsterixJavaClient.java
index 675bb9b..58a7f09 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/java/AsterixJavaClient.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/api/java/AsterixJavaClient.java
@@ -37,6 +37,7 @@
 import org.apache.asterix.translator.IStatementExecutorFactory;
 import org.apache.asterix.translator.SessionConfig;
 import org.apache.asterix.translator.SessionConfig.OutputFormat;
+import org.apache.asterix.translator.SessionConfig.PlanFormat;
 import org.apache.asterix.translator.SessionOutput;
 import org.apache.hyracks.api.client.IHyracksClientConnection;
 import org.apache.hyracks.api.job.JobSpecification;
@@ -80,12 +81,20 @@
     }
 
     public void compile() throws Exception {
-        compile(true, false, false, false, false, false, false);
+        compile(true, false, true, false, false, false, false);
     }
 
     public void compile(boolean optimize, boolean printRewrittenExpressions, boolean printLogicalPlan,
             boolean printOptimizedPlan, boolean printPhysicalOpsOnly, boolean generateBinaryRuntime, boolean printJob)
             throws Exception {
+        compile(optimize, printRewrittenExpressions, printLogicalPlan, printOptimizedPlan, printPhysicalOpsOnly,
+                generateBinaryRuntime, printJob, PlanFormat.STRING);
+    }
+
+    public void compile(boolean optimize, boolean printRewrittenExpressions, boolean printLogicalPlan,
+            boolean printOptimizedPlan, boolean printPhysicalOpsOnly, boolean generateBinaryRuntime, boolean printJob,
+            PlanFormat pformat)
+            throws Exception {
         queryJobSpec = null;
         dmlJobs = null;
 
@@ -101,7 +110,7 @@
         List<Statement> statements = parser.parse();
         MetadataManager.INSTANCE.init();
 
-        SessionConfig conf = new SessionConfig(OutputFormat.ADM, optimize, true, generateBinaryRuntime);
+        SessionConfig conf = new SessionConfig(OutputFormat.ADM, optimize, true, generateBinaryRuntime, pformat);
         conf.setOOBData(false, printRewrittenExpressions, printLogicalPlan, printOptimizedPlan, printJob);
         if (printPhysicalOpsOnly) {
             conf.set(SessionConfig.FORMAT_ONLY_PHYSICAL_OPS, true);
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/drivers/AsterixClientDriver.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/drivers/AsterixClientDriver.java
index ba44833..14a5fe0 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/drivers/AsterixClientDriver.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/drivers/AsterixClientDriver.java
@@ -53,8 +53,8 @@
         }
         boolean exec = new Boolean(acc.execute);
         IHyracksClientConnection hcc = exec ? new HyracksConnection("localhost", acc.hyracksPort) : null;
-        AsterixJavaClient q = compileQuery(hcc, acc.getArguments().get(0), new Boolean(acc.optimize),
-                new Boolean(acc.onlyPhysical), exec || new Boolean(acc.hyracksJob));
+        AsterixJavaClient q = compileQuery(hcc, acc.getArguments().get(0), new Boolean(acc.optimize), false,
+                exec || new Boolean(acc.hyracksJob));
         if (exec) {
             q.execute();
         }
@@ -64,8 +64,9 @@
             boolean onlyPhysical, boolean createBinaryRuntime) throws Exception {
         ILangCompilationProvider compilationProvider = new AqlCompilationProvider();
         FileReader reader = new FileReader(filename);
-        AsterixJavaClient q = new AsterixJavaClient(null, hcc, reader, compilationProvider,
-                new DefaultStatementExecutorFactory(), new StorageComponentProvider());
+        AsterixJavaClient q =
+                new AsterixJavaClient(null, hcc, reader, compilationProvider, new DefaultStatementExecutorFactory(),
+                        new StorageComponentProvider());
         q.compile(optimize, true, true, true, onlyPhysical, createBinaryRuntime, createBinaryRuntime);
         return q;
     }
diff --git a/asterixdb/asterix-app/src/main/resources/webui/querytemplate.html b/asterixdb/asterix-app/src/main/resources/webui/querytemplate.html
index 383754e..1157c27 100644
--- a/asterixdb/asterix-app/src/main/resources/webui/querytemplate.html
+++ b/asterixdb/asterix-app/src/main/resources/webui/querytemplate.html
@@ -19,24 +19,24 @@
 <!DOCTYPE html>
 <html lang="en">
 <head>
-<meta name="description" content="ASTERIX WEB PAGE" />
-<meta name="viewport" content="width=device-width, initial-scale=1.0">
-<link
-	href='http://fonts.googleapis.com/css?family=Bitter|PT+Sans+Caption|Open+Sans'
-	rel='stylesheet' type='text/css'>
-<script src="/webui/static/js/jquery.min.js"></script>
+  <meta name="description" content="ASTERIX WEB PAGE" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <link
+          href='http://fonts.googleapis.com/css?family=Bitter|PT+Sans+Caption|Open+Sans'
+          rel='stylesheet' type='text/css'>
+  <script src="/webui/static/js/jquery.min.js"></script>
 
-<link href="/webui/static/css/bootstrap.min.css" rel="stylesheet"
-	type="text/css" />
-<link href="/webui/static/css/bootstrap-responsive.min.css"
-	rel="stylesheet" type="text/css" />
+  <link href="/webui/static/css/bootstrap.min.css" rel="stylesheet"
+        type="text/css" />
+  <link href="/webui/static/css/bootstrap-responsive.min.css"
+        rel="stylesheet" type="text/css" />
 
-<script src="/webui/static/js/bootstrap.min.js"></script>
+  <script src="/webui/static/js/bootstrap.min.js"></script>
 
-<link href="/webui/static/css/style.css" rel="stylesheet"
-	type="text/css" />
-<script src="/webui/static/js/jquery.json-viewer.js"></script>
-<link href="/webui/static/css/jquery.json-viewer.css" type="text/css" rel="stylesheet" />
+  <link href="/webui/static/css/style.css" rel="stylesheet"
+        type="text/css" />
+  <script src="/webui/static/js/jquery.json-viewer.js"></script>
+  <link href="/webui/static/css/jquery.json-viewer.css" type="text/css" rel="stylesheet" />
 
   <script type="text/javascript">
 $(document).ready(function() {
@@ -169,13 +169,13 @@
                 }
             }
 
-            /* Handling Pretty JSON */
+            /* Handling Pretty JSON-query result */
             var resultFormat = $('#output-format option:checked').text();
             if ( resultFormat == 'JSON (formatted)') {
               $('.result-content').each(
                   function(idx) {
                     var results = $(this).text().split('\n');
-                    $(this).css('padding-left', '20px');
+                     $(this).css('padding-left', '20px');
                     $(this).text('');
                     for (var iter1 = 0; iter1 < results.length - 1; iter1++) {
                       if (results[iter1].length < 1) {
@@ -183,13 +183,39 @@
                       }
                       var resultJSON = $.parseJSON(results[iter1]);
                       $(this).append($('<div/>').attr("id", "json-record"+idx+"-"+iter1));
-                      $('#json-record'+idx+"-"+iter1).jsonViewer(resultJSON, {collapsed: true, level: 1});
+                      $('#json-record'+idx+"-"+iter1).jsonViewer(resultJSON, {collapsed: true, level: 10});
                     }
                   }
                 );
             }
 
 
+            /* Handling Pretty JSON-logical plan */
+            var planFormat = $('#plan-format option:checked').text();
+            $('.query-plan').addClass("outer");
+            $('.query-optimized-plan').addClass("outer");
+            if ( planFormat == 'JSON (formatted)') {
+              $('.query-plan').each(
+                  function() {
+                   var planSt = $(this).text();
+                    $(this).text('');
+                    var planJSON = $.parseJSON(planSt);
+                    $(this).append($('<div/>').attr("id", "json-queryPlan"));
+                    $('#json-queryPlan').jsonViewer(planJSON, {collapsed: false, level: 10});
+                  }
+              );
+              $('.query-optimized-plan').each(
+                  function() {
+                   var opPlanSt = $(this).text();
+                    $(this).text('');
+                    var opPlanJSON = $.parseJSON(opPlanSt);
+                    $(this).append($('<div/>').attr("id", "json-queryOptimizedPlan").attr("class","inner"));
+                    $('#json-queryOptimizedPlan').jsonViewer(opPlanJSON, {collapsed: false, level: 10});
+                  }
+              );
+            }
+
+
             var contentString = data.toString();
             if (contentString.indexOf(durPattern) != -1) {
                 $('<div/>')
@@ -203,106 +229,129 @@
 });
 </script>
 
-<meta charset=utf-8 />
-<title>AsterixDB Web Interface</title>
+  <meta charset=utf-8 />
+  <title>AsterixDB Web Interface</title>
 </head>
 
 <body>
-  <div class="navbar navbar-fixed-top">
-    <div class="navbar-inner">
-      <div class="container">
-        <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
-          <span class="icon-bar"></span>
-          <span class="icon-bar"></span>
-          <span class="icon-bar"></span>
-        </a>
+<div class="navbar navbar-fixed-top">
+  <div class="navbar-inner">
+    <div class="container">
+      <a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
+        <span class="icon-bar"></span>
+        <span class="icon-bar"></span>
+        <span class="icon-bar"></span>
+      </a>
 
-        <!-- Temporary logo placeholder -->
-        <a class="brand" href="#"><img src="/webui/static/img/finalasterixlogo.png"></a>
+      <!-- Temporary logo placeholder -->
+      <a class="brand" href="#"><img src="/webui/static/img/finalasterixlogo.png"></a>
 
-        <div class="nav-collapse collapse">
-          <ul class="nav">
-            <li><a href="https://asterixdb.apache.org/" target="_blank">
-                    Open source<img class="extarget" src="/webui/static/img/targetlink.png"/></a></li>
-            <li><a href="https://issues.apache.org/jira/browse/ASTERIXDB" target="_blank">
-                    File issues<img class="extarget" src="/webui/static/img/targetlink.png"/></a></li>
-            <li><a href="https://ci.apache.org/projects/asterixdb/index.html" target="_blank">
-                    Documentation<img class="extarget" src="/webui/static/img/targetlink.png"/></a></li>
-            <li><a href="https://asterixdb.apache.org/community.html" target="_blank">
-                    Contact<img class="extarget" src="/webui/static/img/targetlink.png"/></a></li>
-          </ul>
-        </div><!--/.nav-collapse -->
-      </div>
+      <div class="nav-collapse collapse">
+        <ul class="nav">
+          <li><a href="https://asterixdb.apache.org/" target="_blank">
+            Open source<img class="extarget" src="/webui/static/img/targetlink.png"/></a></li>
+          <li><a href="https://issues.apache.org/jira/browse/ASTERIXDB" target="_blank">
+            File issues<img class="extarget" src="/webui/static/img/targetlink.png"/></a></li>
+          <li><a href="https://ci.apache.org/projects/asterixdb/index.html" target="_blank">
+            Documentation<img class="extarget" src="/webui/static/img/targetlink.png"/></a></li>
+          <li><a href="https://asterixdb.apache.org/community.html" target="_blank">
+            Contact<img class="extarget" src="/webui/static/img/targetlink.png"/></a></li>
+        </ul>
+      </div><!--/.nav-collapse -->
     </div>
   </div>
+</div>
 
-  <div class="content">
+<div class="content">
     <div class="container">
-      <div class="row-fluid">
+        <div class="row-fluid">
 
-        <div class="span6">
+            <div class="span6">
 
-          <form id="queryform" class="form-horizontal" method="post">
-            <div style="margin-bottom: 1em;">
-              <label class="query">Query</label>
-              <textarea rows="10" id="qry" name="query" class="query" value="%s" placeholder="Type your query ..."></textarea>
+                <form id="queryform" class="form-horizontal" method="post">
+                    <div style="margin-bottom: 1em;">
+                        <label class="query">Query</label>
+                        <textarea rows="10" id="qry" name="query" class="query" value="%s"
+                                  placeholder="Type your query ..."></textarea>
+                    </div>
+
+                    <div class="btn-group">
+                        <button id="checkboxes-on" class="btn">
+                            <i id="opts" class="icon-ok" style="display:none;"></i>Select Options
+                        </button>
+                        <button id="clear-query-button" class="btn">Clear Query</button>
+                        <!-- <button id="checkboxes-off" class="btn">Clear All Options</button> -->
+                        <button type="submit" id="run-btn" class="btn btn-custom-darken">Run</button>
+                    </div>
+
+                    <div>
+                        <label id="query-language" class="optlabel"> Query Language:<br/>
+                            <select name="query-language" class="btn btn-width">
+                                <option selected value="SQLPP">SQL++</option>
+                                <option value="AQL">AQL</option>
+                            </select>
+                        </label>
+                        <label id="output-format" class="optlabel"> Output Format:<br/>
+                            <select name="output-format" class="btn btn-width">
+                                <option selected value="CLEAN_JSON">JSON</option>
+                                <option value="CLEAN_JSON">JSON (formatted)</option>
+                                <option value="ADM">ADM</option>
+                                <option value="CSV">CSV (no header)</option>
+                                <option value="CSV-Header">CSV (with header)</option>
+                                <option value="LOSSLESS_JSON">JSON (lossless)</option>
+                            </select>
+                        </label>
+                        <label id="plan-format" class="optlabel"> Plan Format:<br/>
+                            <select name="plan-format"  class="btn btn-width">
+                                <option selected value="JSON">JSON</option>
+                                <option value="CLEAN_JSON">JSON (formatted)</option>
+                                <option value="STRING">String</option>
+                            </select>
+                        </label>
+                        <label class="optlabel"><input type="checkbox" name="wrapper-array" value="true"/> Wrap results
+                            in outer array</label>
+                        <label class="checkbox optlabel"><input type="checkbox" name="print-expr-tree" value="true"/>
+                            Print parsed expressions</label>
+                        <label class="checkbox optlabel"><input type="checkbox" name="print-rewritten-expr-tree"
+                                                                value="true"/> Print rewritten expressions</label>
+                        <div><label class="checkbox optlabel"><input type="checkbox"
+                                                                                         name="print-logical-plan"
+                                                                                         value="true"/> Print logical
+                            plan</label></div>
+
+                     <div><label class="checkbox optlabel"><input type="checkbox"
+                                                                                         name="print-optimized-logical-plan"
+                                                                                         value="true"/> Print optimized
+                            logical plan</label></div>
+
+                        <label class="checkbox optlabel"><input type="checkbox" name="print-job" value="true"/> Print
+                            Hyracks job</label>
+                        <label class="optlabel"><input type="checkbox" name="execute-query" value="true" checked/>
+                            Execute query</label>
+                    </div>
+                </form>
             </div>
 
-            <div class="btn-group">
-              <button id="checkboxes-on" class="btn">
-                  <i id="opts" class="icon-ok" style="display:none;"></i>Select Options</button>
-              <button id="clear-query-button" class="btn">Clear Query</button>
-              <!-- <button id="checkboxes-off" class="btn">Clear All Options</button> -->
-              <button type="submit" id="run-btn" class="btn btn-custom-darken">Run</button>
+            <div class="span6">
+                <div class="output">
+                    <label id="output-heading" class="heading">Output</label>
+                    <div id="output-message" class="message">
+                    </div>
+                </div>
             </div>
-
-            <div>
-              <label id="query-language" class="optlabel"> Query Language:<br/>
-                <select name="query-language" class="btn">
-                  <option selected value="SQLPP">SQL++</option>
-                  <option value="AQL">AQL</option>
-                </select>
-              </label>
-              <label id="output-format" class="optlabel"> Output Format:<br/>
-                <select name="output-format" class="btn">
-                  <option selected value="CLEAN_JSON">JSON</option>
-                  <option value="CLEAN_JSON">JSON (formatted)</option>
-                  <option value="ADM">ADM</option>
-                  <option value="CSV">CSV (no header)</option>
-                  <option value="CSV-Header">CSV (with header)</option>
-                  <option value="LOSSLESS_JSON">JSON (lossless)</option>
-                </select>
-              </label>
-              <label class="optlabel"><input type="checkbox" name="wrapper-array" value="true" /> Wrap results in outer array</label>
-              <label class="checkbox optlabel"><input type="checkbox" name="print-expr-tree" value="true" /> Print parsed expressions</label>
-              <label class="checkbox optlabel"><input type="checkbox" name="print-rewritten-expr-tree" value="true" /> Print rewritten expressions</label>
-              <label class="checkbox optlabel"><input type="checkbox" name="print-logical-plan" value="true" /> Print logical plan</label>
-              <label class="checkbox optlabel"><input type="checkbox" name="print-optimized-logical-plan" value="true" /> Print optimized logical plan</label>
-              <label class="checkbox optlabel"><input type="checkbox" name="print-job" value="true" /> Print Hyracks job</label>
-              <label class="optlabel"><input type="checkbox" name="execute-query" value="true" checked/> Execute query</label>
-            </div>
-          </form>
-       </div>
-
-       <div class="span6">
-         <div class="output">
-           <label id="output-heading" class="heading">Output</label>
-           <div id="output-message" class="message">
-           </div>
-         </div>
-       </div>
-
-      </div>
+        </div>
     </div>
 </div>
-  <div class="footer">
-    <section class="line"><hr></section>
-    <section class="content">
-      <section class="left">
-      </section>
-      <section class="right">
-      </section>
+<div class="footer">
+    <section class="line">
+        <hr>
     </section>
-  </div>
+    <section class="content">
+        <section class="left">
+        </section>
+        <section class="right">
+        </section>
+    </section>
+</div>
 </body>
 </html>
diff --git a/asterixdb/asterix-app/src/main/resources/webui/static/css/style.css b/asterixdb/asterix-app/src/main/resources/webui/static/css/style.css
index b9b733c..1982e3a 100644
--- a/asterixdb/asterix-app/src/main/resources/webui/static/css/style.css
+++ b/asterixdb/asterix-app/src/main/resources/webui/static/css/style.css
@@ -230,3 +230,33 @@
 .span6 {
     padding: 24px;
 }
+
+.inline-half {
+    display: inline-block;
+    width: 50%;
+    float:left;
+}
+.inline-btn-width{
+    display: inline-block;
+    width: 58%;
+}
+
+.btn-width{
+    width: 30%;
+}
+.inner{
+    float : none;
+}
+
+pre.outer {
+    padding-left :20px;
+    overflow-x : scroll !important;
+    overflow-y : scroll !important;
+    word-wrap : normal !important;
+    word-break : normal !important;
+}
+
+.wrapper {
+    overflow: hidden;
+}
+
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/jsonplan/JsonLogicalPlanTest.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/jsonplan/JsonLogicalPlanTest.java
new file mode 100644
index 0000000..77d0130
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/jsonplan/JsonLogicalPlanTest.java
@@ -0,0 +1 @@
+/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.asterix.test.jsonplan;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.logging.Logger;

import org.apache.asterix.api.common.AsterixHyracksIntegrationUtil;
import org.apache.asterix.api.java.AsterixJavaClient;
import org.apache.asterix.app.translator.DefaultStatementExecutorFactory;
import org.apache.asterix.common.config.GlobalConfig;
import org.apache.asterix.common.context.IStorageComponentProvider;
import org.apache.asterix.common.dataflow.ICcApplicationContext;
import org.apache.asterix.common.exceptions.AsterixException;
import org.apache.asterix.compiler.provider.AqlCompilationProvider;
import org.apache.asterix.compiler.provider.ILangCompilationProvider;
import org.apache.asterix.compiler.provider.SqlppCompilationProvider;
import org.apache.asterix.external.util.ExternalDataConstants;
import org.apache.asterix.external.util.IdentitiyResolverFactory;
import org.apache.asterix.file.StorageComponentProvider;
import org.apache.asterix.test.base.AsterixTestHelper;
import org.apache.asterix.test.common.TestHelper;
import org.apache.asterix.test.runtime.HDFSCluster;
import org.apache.asterix.translator.IStatementExecutorFactory;
import org.apache.asterix.translator.SessionConfig.PlanFormat;
import org.apache.hyracks.api.client.IHyracksClientConnection;
import org.junit.AfterClass;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.internal.AssumptionViolatedException;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;

@RunWith(Parameterized.class)
public class JsonLogicalPlanTest {

    private static final Logger LOGGER =
            Logger.getLogger(org.apache.asterix.test.jsonplan.JsonLogicalPlanTest.class.getName());

    protected static final String SEPARATOR = File.separator;
    private static final String EXTENSION_AQL = "aql";
    private static final String EXTENSION_SQLPP = "sqlpp";
    private static final String EXTENSION_RESULT = "plan";
    private static final String FILENAME_IGNORE = "ignore.txt";
    private static final String FILENAME_ONLY = "only.txt";
    private static final String PATH_BASE =
            "src" + SEPARATOR + "test" + SEPARATOR + "resources" + SEPARATOR + "optimizerts" + SEPARATOR;
    private static final String PATH_QUERIES = PATH_BASE + "queries" + SEPARATOR;
    protected static String PATH_ACTUAL = "target" + File.separator + "jplantest" + SEPARATOR;
    protected static boolean optimized = false;

    private static final ArrayList<String> ignore = AsterixTestHelper.readTestListFile(FILENAME_IGNORE, PATH_BASE);
    private static final ArrayList<String> only = AsterixTestHelper.readTestListFile(FILENAME_ONLY, PATH_BASE);
    protected static String TEST_CONFIG_FILE_NAME = "asterix-build-configuration.xml";
    private static final ILangCompilationProvider aqlCompilationProvider = new AqlCompilationProvider();
    private static final ILangCompilationProvider sqlppCompilationProvider = new SqlppCompilationProvider();
    protected static ILangCompilationProvider extensionLangCompilationProvider = null;
    protected static IStatementExecutorFactory statementExecutorFactory = new DefaultStatementExecutorFactory();
    protected static IStorageComponentProvider storageComponentProvider = new StorageComponentProvider();

    protected static AsterixHyracksIntegrationUtil integrationUtil = new AsterixHyracksIntegrationUtil();

    @BeforeClass
    public static void setUp() throws Exception {
        System.setProperty(GlobalConfig.CONFIG_FILE_PROPERTY, TEST_CONFIG_FILE_NAME);
        final File outdir = new File(PATH_ACTUAL);
        outdir.mkdirs();

        HDFSCluster.getInstance().setup();

        integrationUtil.init(true);
        // Set the node resolver to be the identity resolver that expects node names
        // to be node controller ids; a valid assumption in test environment.
        System.setProperty(ExternalDataConstants.NODE_RESOLVER_FACTORY_PROPERTY,
                IdentitiyResolverFactory.class.getName());
    }

    @AfterClass
    public static void tearDown() throws Exception {
        File outdir = new File(PATH_ACTUAL);
        File[] files = outdir.listFiles();
        if (files == null || files.length == 0) {
            outdir.delete();
        }

        HDFSCluster.getInstance().cleanup();

        integrationUtil.deinit(true);
    }

    private static void suiteBuildPerFile(File file, Collection<Object[]> testArgs, String path) {
        if (file.isDirectory() && !file.getName().startsWith(".")) {
            for (File innerfile : file.listFiles()) {
                String subdir = innerfile.isDirectory() ? path + innerfile.getName() + SEPARATOR : path;
                suiteBuildPerFile(innerfile, testArgs, subdir);
            }
        }
        if (file.isFile() && (file.getName().endsWith(EXTENSION_AQL) || file.getName().endsWith(EXTENSION_SQLPP))) {
            String resultFileName = AsterixTestHelper.extToResExt(file.getName(), EXTENSION_RESULT);
            File actualFile = new File(PATH_ACTUAL + SEPARATOR + path + resultFileName);
            testArgs.add(new Object[] { file, actualFile });
        }
    }

    @Parameters(name = "JsonLogicalPlanTest {index}: {0}")
    public static Collection<Object[]> tests() {
        Collection<Object[]> testArgs = new ArrayList<>();
        if (only.isEmpty()) {
            suiteBuildPerFile(new File(PATH_QUERIES), testArgs, "");
        } else {
            for (String path : only) {
                suiteBuildPerFile(new File(PATH_QUERIES + path), testArgs,
                        path.lastIndexOf(SEPARATOR) < 0 ? "" : path.substring(0, path.lastIndexOf(SEPARATOR) + 1));
            }
        }
        return testArgs;
    }

    private final File actualFile;
    private final File queryFile;

    public JsonLogicalPlanTest(final File queryFile, final File actualFile) {
        this.queryFile = queryFile;
        this.actualFile = actualFile;
    }

    @Test
    public void test() throws Exception {
        try {
            String queryFileShort =
                    queryFile.getPath().substring(PATH_QUERIES.length()).replace(SEPARATOR.charAt(0), '/');
            if (!only.isEmpty()) {
                boolean toRun = TestHelper.isInPrefixList(only, queryFileShort);
                if (!toRun) {
                    LOGGER.info("SKIP TEST: \"" + queryFile.getPath()
                            + "\" \"only.txt\" not empty and not in \"only.txt\".");
                }
                Assume.assumeTrue(toRun);
            }
            boolean skipped = TestHelper.isInPrefixList(ignore, queryFileShort);
            if (skipped) {
                LOGGER.info("SKIP TEST: \"" + queryFile.getPath() + "\" in \"ignore.txt\".");
            }
            Assume.assumeTrue(!skipped);

            LOGGER.info("RUN TEST: \"" + queryFile.getPath() + "\"");
            Reader query = new BufferedReader(new InputStreamReader(new FileInputStream(queryFile), "UTF-8"));

            // Forces the creation of actualFile.
            actualFile.getParentFile().mkdirs();

            PrintWriter plan = new PrintWriter(actualFile);
            ILangCompilationProvider provider =
                    queryFile.getName().endsWith("aql") ? aqlCompilationProvider : sqlppCompilationProvider;
            if (extensionLangCompilationProvider != null) {
                provider = extensionLangCompilationProvider;
            }
            IHyracksClientConnection hcc = integrationUtil.getHyracksClientConnection();
            AsterixJavaClient asterix =
                    new AsterixJavaClient((ICcApplicationContext) integrationUtil.cc.getApplicationContext(), hcc,
                            query, plan, provider, statementExecutorFactory, storageComponentProvider);
            try {
                asterix.compile(true, false, !optimized, optimized, false, false, false, PlanFormat.JSON);

            } catch (AsterixException e) {
                plan.close();
                query.close();
                throw new Exception("Compile ERROR for " + queryFile + ": " + e.getMessage(), e);
            }
            plan.close();
            query.close();

            BufferedReader readerActual =
                    new BufferedReader(new InputStreamReader(new FileInputStream(actualFile), "UTF-8"));
            String lineActual, objectActual = "";
            boolean firstPlan = false;
            while ((lineActual = readerActual.readLine()) != null) {
                if (lineActual.contains("--")) {
                    if (firstPlan) {
                        break;
                    }
                    firstPlan = true;

                } else {
                    objectActual = objectActual + lineActual;
                }
            }

            try {
                final JsonParser parser = new ObjectMapper().getJsonFactory().createJsonParser(objectActual);
                while (parser.nextToken() != null) {
                }
            } finally {
                readerActual.close();
            }

        } catch (Exception e) {
            if (!(e instanceof AssumptionViolatedException)) {
                LOGGER.severe("Test \"" + queryFile.getPath() + "\" FAILED!");
                throw new Exception("Test \"" + queryFile.getPath() + "\" FAILED!", e);
            } else {
                throw e;
            }
        }
    }
}
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/jsonplan/JsonOptimizedLogicalPlanTest.java b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/jsonplan/JsonOptimizedLogicalPlanTest.java
new file mode 100644
index 0000000..b8e4595
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/java/org/apache/asterix/test/jsonplan/JsonOptimizedLogicalPlanTest.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.asterix.test.jsonplan;
+
+import java.io.File;
+import java.util.logging.Logger;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class JsonOptimizedLogicalPlanTest extends JsonLogicalPlanTest {
+
+    private static final Logger LOGGER =
+            Logger.getLogger(org.apache.asterix.test.jsonplan.JsonOptimizedLogicalPlanTest.class.getName());
+
+    public JsonOptimizedLogicalPlanTest(File queryFile, File actualFile) {
+        super(queryFile, actualFile);
+        optimized = true;
+        PATH_ACTUAL = "target" + File.separator + "joptplantest" + SEPARATOR;
+    }
+}
\ No newline at end of file
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/AbstractLogicalOperator.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/AbstractLogicalOperator.java
index 4686f32..8b38a2b 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/AbstractLogicalOperator.java
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/AbstractLogicalOperator.java
@@ -19,7 +19,7 @@
 package org.apache.hyracks.algebricks.core.algebra.operators.logical;
 
 import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -63,7 +63,7 @@
 
     private AbstractLogicalOperator.ExecutionMode mode = AbstractLogicalOperator.ExecutionMode.UNPARTITIONED;
     protected IPhysicalOperator physicalOperator;
-    private final Map<String, Object> annotations = new HashMap<>();
+    private final Map<String, Object> annotations = new IdentityHashMap<String, Object>();
     private boolean bJobGenEnabled = true;
 
     protected final List<Mutable<ILogicalOperator>> inputs;
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/AbstractLogicalOperatorPrettyPrintVisitor.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/AbstractLogicalOperatorPrettyPrintVisitor.java
new file mode 100644
index 0000000..140ba80
--- /dev/null
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/AbstractLogicalOperatorPrettyPrintVisitor.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.hyracks.algebricks.core.algebra.prettyprint;
+
+import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalOperator;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalPlan;
+import org.apache.hyracks.algebricks.core.algebra.base.IPhysicalOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractLogicalOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractOperatorWithNestedPlans;
+import org.apache.hyracks.algebricks.core.algebra.visitors.ILogicalExpressionVisitor;
+import org.apache.hyracks.algebricks.core.algebra.visitors.ILogicalOperatorVisitor;
+
+public abstract class AbstractLogicalOperatorPrettyPrintVisitor implements ILogicalOperatorVisitor<Void, Integer> {
+    ILogicalExpressionVisitor<String, Integer> exprVisitor;
+    AlgebricksAppendable buffer;
+
+    public AbstractLogicalOperatorPrettyPrintVisitor() {
+        this(new AlgebricksAppendable());
+    }
+
+    public AbstractLogicalOperatorPrettyPrintVisitor(Appendable app) {
+        this(new AlgebricksAppendable(app), new LogicalExpressionPrettyPrintVisitor());
+    }
+
+    public AbstractLogicalOperatorPrettyPrintVisitor(AlgebricksAppendable buffer) {
+        this(buffer, new LogicalExpressionPrettyPrintVisitor());
+    }
+
+    public AbstractLogicalOperatorPrettyPrintVisitor(AlgebricksAppendable buffer,
+            ILogicalExpressionVisitor<String, Integer> exprVisitor) {
+        reset(buffer);
+        this.exprVisitor = exprVisitor;
+    }
+
+    public AlgebricksAppendable reset(AlgebricksAppendable buffer) {
+        AlgebricksAppendable old = this.buffer;
+        this.buffer = buffer;
+        return old;
+    }
+
+    public AlgebricksAppendable get() {
+        return buffer;
+    }
+
+    @Override
+    public String toString() {
+        return buffer.toString();
+    }
+
+    CharSequence str(Object o) {
+        return String.valueOf(o);
+    }
+
+    protected static void appendln(AlgebricksAppendable buf, String s) throws AlgebricksException {
+        buf.append(s);
+        buf.append("\n");
+    }
+
+    protected static void append(AlgebricksAppendable buf, String s) throws AlgebricksException {
+        buf.append(s);
+    }
+
+    protected static void pad(AlgebricksAppendable buf, int indent) throws AlgebricksException {
+        for (int i = 0; i < indent; ++i) {
+            buf.append(' ');
+        }
+    }
+
+    public static void printPhysicalOperator(AbstractLogicalOperator op, int indent, AlgebricksAppendable out)
+            throws AlgebricksException {
+        IPhysicalOperator pOp = op.getPhysicalOperator();
+        pad(out, indent);
+        appendln(out, "-- " + pOp.toString() + "  |" + op.getExecutionMode() + "|");
+        if (op.hasNestedPlans()) {
+            AbstractOperatorWithNestedPlans opNest = (AbstractOperatorWithNestedPlans) op;
+            for (ILogicalPlan p : opNest.getNestedPlans()) {
+                pad(out, indent + 8);
+                appendln(out, "{");
+                printPhysicalOps(p, out, indent + 10);
+                pad(out, indent + 8);
+                appendln(out, "}");
+            }
+        }
+        for (Mutable<ILogicalOperator> i : op.getInputs()) {
+            printPhysicalOperator((AbstractLogicalOperator) i.getValue(), indent + 2, out);
+        }
+    }
+
+    public static void printPhysicalOps(ILogicalPlan plan, AlgebricksAppendable out, int indent)
+            throws AlgebricksException {
+        for (Mutable<ILogicalOperator> root : plan.getRoots()) {
+            printPhysicalOperator((AbstractLogicalOperator) root.getValue(), indent, out);
+        }
+    }
+}
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitor.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitor.java
index 2139627..fe63b89 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitor.java
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitor.java
@@ -25,18 +25,21 @@
 import org.apache.hyracks.algebricks.common.utils.Pair;
 import org.apache.hyracks.algebricks.common.utils.Triple;
 import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalOperator;
 import org.apache.hyracks.algebricks.core.algebra.base.ILogicalPlan;
+import org.apache.hyracks.algebricks.core.algebra.base.IPhysicalOperator;
 import org.apache.hyracks.algebricks.core.algebra.base.LogicalVariable;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractLogicalOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractOperatorWithNestedPlans;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractUnnestMapOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.AggregateOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.AssignOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.DataSourceScanOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.DelegateOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.DistinctOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.DistributeResultOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.EmptyTupleSourceOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.ExchangeOperator;
-import org.apache.hyracks.algebricks.core.algebra.operators.logical.DelegateOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.GroupByOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.IndexInsertDeleteUpsertOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.InnerJoinOperator;
@@ -65,48 +68,50 @@
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.WriteOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.WriteResultOperator;
 import org.apache.hyracks.algebricks.core.algebra.visitors.ILogicalExpressionVisitor;
-import org.apache.hyracks.algebricks.core.algebra.visitors.ILogicalOperatorVisitor;
 
-public class LogicalOperatorPrettyPrintVisitor implements ILogicalOperatorVisitor<Void, Integer> {
-
-    ILogicalExpressionVisitor<String, Integer> exprVisitor;
-    AlgebricksAppendable buffer;
+public class LogicalOperatorPrettyPrintVisitor extends AbstractLogicalOperatorPrettyPrintVisitor {
 
     public LogicalOperatorPrettyPrintVisitor() {
-        this(new AlgebricksAppendable());
-    }
-
-    public LogicalOperatorPrettyPrintVisitor(Appendable app) {
-        this(new AlgebricksAppendable(app), new LogicalExpressionPrettyPrintVisitor());
-    }
-
-    public LogicalOperatorPrettyPrintVisitor(AlgebricksAppendable buffer) {
-        this(buffer, new LogicalExpressionPrettyPrintVisitor());
+        super();
     }
 
     public LogicalOperatorPrettyPrintVisitor(AlgebricksAppendable buffer,
             ILogicalExpressionVisitor<String, Integer> exprVisitor) {
-        reset(buffer);
-        this.exprVisitor = exprVisitor;
+        super(buffer, exprVisitor);
     }
 
-    public AlgebricksAppendable reset(AlgebricksAppendable buffer) {
-        AlgebricksAppendable old = this.buffer;
-        this.buffer = buffer;
-        return old;
+    public LogicalOperatorPrettyPrintVisitor(AlgebricksAppendable buffer) {
+        super(buffer);
     }
 
-    public AlgebricksAppendable get() {
-        return buffer;
+    public LogicalOperatorPrettyPrintVisitor(Appendable app) {
+        super(app);
     }
 
-    @Override
-    public String toString() {
-        return buffer.toString();
+    public static void printPlan(ILogicalPlan plan, LogicalOperatorPrettyPrintVisitor pvisitor, int indent)
+            throws AlgebricksException {
+        for (Mutable<ILogicalOperator> root : plan.getRoots()) {
+            printOperator((AbstractLogicalOperator) root.getValue(), pvisitor, indent);
+        }
     }
 
-    CharSequence str(Object o) {
-        return String.valueOf(o);
+    public static void printOperator(AbstractLogicalOperator op, LogicalOperatorPrettyPrintVisitor pvisitor, int indent)
+            throws AlgebricksException {
+        final AlgebricksAppendable out = pvisitor.get();
+        op.accept(pvisitor, indent);
+        IPhysicalOperator pOp = op.getPhysicalOperator();
+
+        if (pOp != null) {
+            out.append("\n");
+            pad(out, indent);
+            appendln(out, "-- " + pOp.toString() + "  |" + op.getExecutionMode() + "|");
+        } else {
+            appendln(out, " -- |" + op.getExecutionMode() + "|");
+        }
+
+        for (Mutable<ILogicalOperator> i : op.getInputs()) {
+            printOperator((AbstractLogicalOperator) i.getValue(), pvisitor, indent + 2);
+        }
     }
 
     @Override
@@ -480,7 +485,7 @@
                 } else {
                     addIndent(indent).append("       {\n");
                 }
-                PlanPrettyPrinter.printPlan(p, this, indent + 10);
+                printPlan(p, this, indent + 10);
                 addIndent(indent).append("       }");
             }
         }
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitorJson.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitorJson.java
new file mode 100644
index 0000000..fefb1e9
--- /dev/null
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitorJson.java
@@ -0,0 +1,748 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.hyracks.algebricks.core.algebra.prettyprint;
+
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+import org.apache.hyracks.algebricks.common.utils.Pair;
+import org.apache.hyracks.algebricks.common.utils.Triple;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalOperator;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalPlan;
+import org.apache.hyracks.algebricks.core.algebra.base.IPhysicalOperator;
+import org.apache.hyracks.algebricks.core.algebra.base.LogicalVariable;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractLogicalOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractOperatorWithNestedPlans;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractUnnestMapOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.AggregateOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.AssignOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.DataSourceScanOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.DelegateOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.DistinctOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.DistributeResultOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.EmptyTupleSourceOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.ExchangeOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.GroupByOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.IndexInsertDeleteUpsertOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.InnerJoinOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.InsertDeleteUpsertOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.InsertDeleteUpsertOperator.Kind;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.IntersectOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterJoinOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterUnnestMapOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterUnnestOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.LimitOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.MaterializeOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.NestedTupleSourceOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.OrderOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.ProjectOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.ReplicateOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.RunningAggregateOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.ScriptOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.SelectOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.SinkOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.SplitOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.SubplanOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.TokenizeOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.UnionAllOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.UnnestMapOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.UnnestOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.WriteOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.WriteResultOperator;
+import org.apache.hyracks.algebricks.core.algebra.visitors.ILogicalExpressionVisitor;
+
+public class LogicalOperatorPrettyPrintVisitorJson extends AbstractLogicalOperatorPrettyPrintVisitor {
+    Map<AbstractLogicalOperator, String> operatorIdentity = new HashMap<>();
+
+    public LogicalOperatorPrettyPrintVisitorJson(Appendable app) {
+        super(app);
+    }
+
+    IdCounter idCounter = new IdCounter();
+
+    public class IdCounter {
+        private int id;
+        private Deque<Integer> prefix;
+
+        public IdCounter() {
+            prefix = new LinkedList<Integer>();
+            prefix.add(1);
+            this.id = 0;
+        }
+
+        public void previousPrefix() {
+            this.id = prefix.removeLast();
+        }
+
+        public void nextPrefix() {
+            prefix.add(this.id);
+            this.id = 0;
+        }
+
+        public String printOperatorId(AbstractLogicalOperator op) {
+            String stringPrefix = "";
+            Object[] values = this.prefix.toArray();
+            for (Object val : values) {
+                stringPrefix = stringPrefix.isEmpty() ? val.toString() : stringPrefix + "." + val.toString();
+            }
+            if (!operatorIdentity.containsKey(op)) {
+                String opId = stringPrefix.isEmpty() ? "" + Integer.toString(++id)
+                        : stringPrefix + "." + Integer.toString(++id);
+                operatorIdentity.put(op, opId);
+            }
+            return operatorIdentity.get(op);
+        }
+    }
+
+    public static void printPlanJson(ILogicalPlan plan, LogicalOperatorPrettyPrintVisitorJson pvisitor, int indent)
+            throws AlgebricksException {
+        for (Mutable<ILogicalOperator> root : plan.getRoots()) {
+            printOperatorJson((AbstractLogicalOperator) root.getValue(), pvisitor, indent);
+        }
+    }
+
+    public static void printOperatorJson(AbstractLogicalOperator op, LogicalOperatorPrettyPrintVisitorJson pvisitor,
+            int indent) throws AlgebricksException {
+        int currentIndent = indent;
+        final AlgebricksAppendable out = pvisitor.get();
+        pad(out, currentIndent);
+        appendln(out, "{");
+        currentIndent++;
+        op.accept(pvisitor, currentIndent);
+        appendln(out, ",");
+        pad(out, currentIndent);
+        append(out, "\"operatorId\" : \"" + pvisitor.idCounter.printOperatorId(op) + "\"");
+        IPhysicalOperator pOp = op.getPhysicalOperator();
+        if (pOp != null) {
+            appendln(out, ",");
+            pad(out, currentIndent);
+            String pOperator = "\"physical-operator\":\"" + pOp.toString() + "\"";
+            append(out, pOperator);
+        }
+        appendln(out, ",");
+        pad(out, currentIndent);
+        append(out, "\"execution-mode\":\"" + op.getExecutionMode() + '"');
+        if (!op.getInputs().isEmpty()) {
+            appendln(out, ",");
+            pad(out, currentIndent);
+            appendln(out, "\"inputs\":[");
+            boolean moreInputes = false;
+            for (Mutable<ILogicalOperator> k : op.getInputs()) {
+                if (moreInputes) {
+                    append(out, ",");
+                }
+                printOperatorJson((AbstractLogicalOperator) k.getValue(), pvisitor, currentIndent + 4);
+                moreInputes = true;
+            }
+            pad(out, currentIndent + 2);
+            appendln(out, "]");
+        }
+        out.append("\n");
+        pad(out, currentIndent - 1);
+        appendln(out, "}");
+    }
+
+    public void variablePrintHelper(List<LogicalVariable> variables, Integer indent) throws AlgebricksException {
+        if (!variables.isEmpty()) {
+            addIndent(0).append(",\n");
+            addIndent(indent).append("\"variables\" :[");
+            boolean first = true;
+            for (LogicalVariable v : variables) {
+                if (!first) {
+                    buffer.append(",");
+                }
+                buffer.append("\"" + str(v) + "\"");
+                first = false;
+            }
+            buffer.append("]");
+        }
+    }
+
+    @Override
+    public Void visitAggregateOperator(AggregateOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"aggregate\"");
+        variablePrintHelper(op.getVariables(), indent);
+
+        return null;
+    }
+
+    @Override
+    public Void visitRunningAggregateOperator(RunningAggregateOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"running-aggregate\"");
+        variablePrintHelper(op.getVariables(), indent);
+        if (!op.getExpressions().isEmpty()) {
+            addIndent(0).append(",\n");
+            pprintExprList(op.getExpressions(), indent);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitEmptyTupleSourceOperator(EmptyTupleSourceOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"empty-tuple-source\"");
+        return null;
+    }
+
+    @Override
+    public Void visitGroupByOperator(GroupByOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"group-by\"");
+
+        if (op.isGroupAll()) {
+            buffer.append(",\n");
+            addIndent(indent).append("\"option\":\"all\"");
+        }
+        if (!op.getGroupByList().isEmpty()) {
+            buffer.append(",\n");
+            addIndent(indent).append("\"group-by-list\":");
+            pprintVeList(op.getGroupByList(), indent);
+        }
+        if (!op.getDecorList().isEmpty()) {
+            buffer.append(",\n");
+            addIndent(indent).append("\"decor-list\":");
+            pprintVeList(op.getDecorList(), indent);
+        }
+        if (!op.getNestedPlans().isEmpty()) {
+            buffer.append(",\n");
+            addIndent(indent).append("\"subplan\":");
+            printNestedPlans(op, indent);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitDistinctOperator(DistinctOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"distinct\"");
+        if (!op.getExpressions().isEmpty()) {
+            addIndent(0).append(",\n");
+            pprintExprList(op.getExpressions(), indent);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitInnerJoinOperator(InnerJoinOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"join\",\n");
+        addIndent(indent)
+                .append("\"condition\":" + "\"" + op.getCondition().getValue().accept(exprVisitor, indent) + "\"");
+        return null;
+    }
+
+    @Override
+    public Void visitLeftOuterJoinOperator(LeftOuterJoinOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"left-outer-join\",\n");
+        addIndent(indent)
+                .append("\"condition\":" + "\"" + op.getCondition().getValue().accept(exprVisitor, indent) + "\"");
+        return null;
+    }
+
+    @Override
+    public Void visitNestedTupleSourceOperator(NestedTupleSourceOperator op, Integer indent)
+            throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"nested-tuple-source\"");
+        return null;
+    }
+
+    @Override
+    public Void visitOrderOperator(OrderOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"order\"");
+        for (Pair<OrderOperator.IOrder, Mutable<ILogicalExpression>> p : op.getOrderExpressions()) {
+            buffer.append(",\n");
+            if (op.getTopK() != -1) {
+                addIndent(indent).append("\"topK\":\"" + op.getTopK() + "\",\n");
+            }
+            String fst = getOrderString(p.first);
+            addIndent(indent).append("\"first\":" + fst + ",\n");
+            addIndent(indent)
+                    .append("\"second\":\"" + p.second.getValue().accept(exprVisitor, indent).replace('"', ' ') + "\"");
+        }
+        return null;
+    }
+
+    private String getOrderString(OrderOperator.IOrder first) {
+        switch (first.getKind()) {
+            case ASC:
+                return "\"ASC\"";
+            case DESC:
+                return "\"DESC\"";
+            default:
+                return first.getExpressionRef().toString();
+        }
+    }
+
+    @Override
+    public Void visitAssignOperator(AssignOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"assign\"");
+        variablePrintHelper(op.getVariables(), indent);
+        if (!op.getExpressions().isEmpty()) {
+            addIndent(0).append(",\n");
+            pprintExprList(op.getExpressions(), indent);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitWriteOperator(WriteOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"write\"");
+        if (!op.getExpressions().isEmpty()) {
+            addIndent(0).append(",\n");
+            pprintExprList(op.getExpressions(), indent);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitDistributeResultOperator(DistributeResultOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"distribute-result\"");
+        if (!op.getExpressions().isEmpty()) {
+            addIndent(0).append(",\n");
+            pprintExprList(op.getExpressions(), indent);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitWriteResultOperator(WriteResultOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"load\",\n");
+        addIndent(indent).append(str(op.getDataSource())).append("\"from\":")
+                .append(op.getPayloadExpression().getValue().accept(exprVisitor, indent) + ",\n");
+        addIndent(indent).append("\"partitioned-by\":{");
+        pprintExprList(op.getKeyExpressions(), indent);
+        addIndent(indent).append("}");
+        return null;
+    }
+
+    @Override
+    public Void visitSelectOperator(SelectOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"select\",\n");
+        addIndent(indent).append("\"expressions\":\""
+                + op.getCondition().getValue().accept(exprVisitor, indent).replace('"', ' ') + "\"");
+        return null;
+    }
+
+    @Override
+    public Void visitProjectOperator(ProjectOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"project\"");
+        variablePrintHelper(op.getVariables(), indent);
+        return null;
+    }
+
+    @Override
+    public Void visitSubplanOperator(SubplanOperator op, Integer indent) throws AlgebricksException {
+        if (!op.getNestedPlans().isEmpty()) {
+            addIndent(indent).append("\"subplan\":");
+            printNestedPlans(op, indent);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitUnionOperator(UnionAllOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"union\"");
+        for (Triple<LogicalVariable, LogicalVariable, LogicalVariable> v : op.getVariableMappings()) {
+            buffer.append(",\n");
+            addIndent(indent)
+                    .append("\"values\":[" + "\"" + v.first + "\"," + "\"" + v.second + "\"," + "\"" + v.third + "\"]");
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitIntersectOperator(IntersectOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"intersect\",\n");
+
+        addIndent(indent).append("\"output-variables\":[");
+        for (int i = 0; i < op.getOutputVars().size(); i++) {
+            if (i > 0) {
+                buffer.append(", ");
+            }
+            buffer.append("\"" + str(op.getOutputVars().get(i)) + "\"");
+        }
+        buffer.append("],");
+        addIndent(indent).append("\"input_variables\":[");
+        for (int i = 0; i < op.getNumInput(); i++) {
+            if (i > 0) {
+                buffer.append(",\n");
+            }
+            buffer.append("[");
+            for (int j = 0; j < op.getInputVariables(i).size(); j++) {
+                if (j > 0) {
+                    buffer.append(", ");
+                }
+                buffer.append("\"" + str(op.getInputVariables(i).get(j)) + "\"");
+            }
+            buffer.append(']');
+        }
+        buffer.append("]");
+        return null;
+    }
+
+    @Override
+    public Void visitUnnestOperator(UnnestOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"unnest\"");
+        variablePrintHelper(op.getVariables(), indent);
+        if (op.getPositionalVariable() != null) {
+            buffer.append(",\n");
+            addIndent(indent).append("\"position\":\"" + op.getPositionalVariable() + "\"");
+        }
+        buffer.append(",\n");
+        addIndent(indent).append("\"expressions\":\""
+                + op.getExpressionRef().getValue().accept(exprVisitor, indent).replace('"', ' ') + "\"");
+        return null;
+    }
+
+    @Override
+    public Void visitLeftOuterUnnestOperator(LeftOuterUnnestOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"outer-unnest\",\n");
+        addIndent(indent).append("\"variables\":[\"" + op.getVariable() + "\"]");
+        if (op.getPositionalVariable() != null) {
+            buffer.append(",\n");
+            addIndent(indent).append("\"position\":" + op.getPositionalVariable());
+        }
+        buffer.append(",\n");
+        addIndent(indent).append("\"expressions\":\""
+                + op.getExpressionRef().getValue().accept(exprVisitor, indent).replace('"', ' ') + "\"");
+        return null;
+    }
+
+    @Override
+    public Void visitUnnestMapOperator(UnnestMapOperator op, Integer indent) throws AlgebricksException {
+        return printAbstractUnnestMapOperator(op, indent, "unnest-map");
+    }
+
+    @Override
+    public Void visitLeftOuterUnnestMapOperator(LeftOuterUnnestMapOperator op, Integer indent)
+            throws AlgebricksException {
+        return printAbstractUnnestMapOperator(op, indent, "left-outer-unnest-map");
+    }
+
+    private Void printAbstractUnnestMapOperator(AbstractUnnestMapOperator op, Integer indent, String opSignature)
+            throws AlgebricksException {
+        AlgebricksAppendable plan = addIndent(indent).append("\"operator\":\"" + opSignature + "\"");
+        variablePrintHelper(op.getVariables(), indent);
+        buffer.append(",\n");
+        addIndent(indent).append("\"expressions\":\""
+                + op.getExpressionRef().getValue().accept(exprVisitor, indent).replace('"', ' ') + "\"");
+        appendFilterInformation(plan, op.getMinFilterVars(), op.getMaxFilterVars(), indent);
+        return null;
+    }
+
+    @Override
+    public Void visitDataScanOperator(DataSourceScanOperator op, Integer indent) throws AlgebricksException {
+        AlgebricksAppendable plan = addIndent(indent).append("\"operator\":\"data-scan\"");
+        if (!op.getProjectVariables().isEmpty()) {
+            addIndent(0).append(",\n");
+            addIndent(indent).append("\"project-variables\":[");
+            boolean first = true;
+            for (LogicalVariable v : op.getProjectVariables()) {
+                if (!first) {
+                    buffer.append(",");
+                }
+                buffer.append("\"" + str(v) + "\"");
+                first = false;
+            }
+            buffer.append("]");
+        }
+        variablePrintHelper(op.getVariables(), indent);
+        if (op.getDataSource() != null) {
+            addIndent(0).append(",\n");
+            addIndent(indent).append("\"data-source\":\"" + op.getDataSource() + "\"");
+        }
+        appendFilterInformation(plan, op.getMinFilterVars(), op.getMaxFilterVars(), indent);
+        return null;
+    }
+
+    private Void appendFilterInformation(AlgebricksAppendable plan, List<LogicalVariable> minFilterVars,
+            List<LogicalVariable> maxFilterVars, Integer indent) throws AlgebricksException {
+        if (minFilterVars != null || maxFilterVars != null) {
+            plan.append(",\n");
+            addIndent(indent);
+            plan.append("\"with-filter-on\":{");
+        }
+        if (minFilterVars != null) {
+            buffer.append("\n");
+            addIndent(indent).append("\"min\":[");
+            boolean first = true;
+            for (LogicalVariable v : minFilterVars) {
+                if (!first) {
+                    buffer.append(",");
+                }
+                buffer.append("\"" + str(v) + "\"");
+                first = false;
+            }
+            buffer.append("]");
+        }
+        if (minFilterVars != null && maxFilterVars != null) {
+            buffer.append(",");
+        }
+        if (maxFilterVars != null) {
+            buffer.append("\n");
+            addIndent(indent).append("\"max\":[");
+            boolean first = true;
+            for (LogicalVariable v : maxFilterVars) {
+                if (!first) {
+                    buffer.append(",");
+                }
+                buffer.append("\"" + str(v) + "\"");
+                first = false;
+            }
+            buffer.append("]");
+        }
+        if (minFilterVars != null || maxFilterVars != null) {
+            plan.append("\n");
+            addIndent(indent).append("}");
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitLimitOperator(LimitOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"limit\",\n");
+        addIndent(indent).append("\"value\":\"" + op.getMaxObjects().getValue().accept(exprVisitor, indent) + "\"");
+        ILogicalExpression offset = op.getOffset().getValue();
+        if (offset != null) {
+            buffer.append(",\n");
+            addIndent(indent).append("\"offset\":\"" + offset.accept(exprVisitor, indent) + "\"");
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitExchangeOperator(ExchangeOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"exchange\"");
+        return null;
+    }
+
+    @Override
+    public Void visitScriptOperator(ScriptOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"script\"");
+        if (!op.getInputVariables().isEmpty()) {
+            addIndent(0).append(",\n");
+            addIndent(indent).append("\"in\":[");
+            boolean first = true;
+            for (LogicalVariable v : op.getInputVariables()) {
+                if (!first) {
+                    buffer.append(",");
+                }
+                buffer.append("\"" + str(v) + "\"");
+                first = false;
+            }
+            buffer.append("]");
+        }
+        if (!op.getOutputVariables().isEmpty()) {
+            addIndent(0).append(",\n");
+            addIndent(indent).append("\"out\":[");
+            boolean first = true;
+            for (LogicalVariable v : op.getOutputVariables()) {
+                if (!first) {
+                    buffer.append(",");
+                }
+                buffer.append("\"" + str(v) + "\"");
+                first = false;
+            }
+            buffer.append("]");
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitReplicateOperator(ReplicateOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"replicate\"");
+        return null;
+    }
+
+    @Override
+    public Void visitSplitOperator(SplitOperator op, Integer indent) throws AlgebricksException {
+        Mutable<ILogicalExpression> branchingExpression = op.getBranchingExpression();
+        addIndent(indent).append("\"operator\":\"split\",\n");
+        addIndent(indent).append("\"" + branchingExpression.getValue().accept(exprVisitor, indent) + "\"");
+        return null;
+    }
+
+    @Override
+    public Void visitMaterializeOperator(MaterializeOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"materialize\"");
+        return null;
+    }
+
+    @Override
+    public Void visitInsertDeleteUpsertOperator(InsertDeleteUpsertOperator op, Integer indent)
+            throws AlgebricksException {
+        String header = "\"operator\":\"" + getIndexOpString(op.getOperation()) + "\",\n";
+        addIndent(indent).append(header);
+        addIndent(indent).append(str("\"data-source\":\"" + op.getDataSource() + "\",\n"));
+        addIndent(indent).append("\"from-record\":\"")
+                .append(op.getPayloadExpression().getValue().accept(exprVisitor, indent) + "\"");
+        if (op.getAdditionalNonFilteringExpressions() != null) {
+            buffer.append(",\n\"meta\":\"");
+            pprintExprList(op.getAdditionalNonFilteringExpressions(), 0);
+            buffer.append("\"");
+        }
+        buffer.append(",\n");
+        addIndent(indent).append("\"partitioned-by\":{");
+        pprintExprList(op.getPrimaryKeyExpressions(), 0);
+        buffer.append("}");
+        if (op.getOperation() == Kind.UPSERT) {
+            addIndent(indent).append(",\n\"out\":{\n");
+            addIndent(indent).append("\"record-before-upsert\":\"" + op.getBeforeOpRecordVar() + "\"");
+            if (op.getBeforeOpAdditionalNonFilteringVars() != null) {
+                buffer.append(",\n");
+                addIndent(indent)
+                        .append("\"additional-before-upsert\":\"" + op.getBeforeOpAdditionalNonFilteringVars() + "\"");
+            }
+            addIndent(indent).append("}");
+        }
+        if (op.isBulkload()) {
+            buffer.append(",\n");
+            addIndent(indent).append("\"bulkload\":\"true\"");
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitIndexInsertDeleteUpsertOperator(IndexInsertDeleteUpsertOperator op, Integer indent)
+            throws AlgebricksException {
+        String header = getIndexOpString(op.getOperation());
+        addIndent(indent).append("\"operator\":\"" + header + "\",\n");
+        addIndent(indent).append("\"index\":\"" + op.getIndexName() + "\",\n");
+        addIndent(indent).append("\"on\":\"").append(str(op.getDataSourceIndex().getDataSource()) + "\",\n");
+        addIndent(indent).append("\"from\":{");
+
+        if (op.getOperation() == Kind.UPSERT) {
+
+            addIndent(indent).append("[\"replace\":\"");
+            pprintExprList(op.getPrevSecondaryKeyExprs(), 0);
+            buffer.append("\",\n");
+            addIndent(indent).append("\"with\":\"");
+            pprintExprList(op.getSecondaryKeyExpressions(), 0);
+            buffer.append("\"}");
+        } else {
+            pprintExprList(op.getSecondaryKeyExpressions(), 0);
+        }
+        buffer.append("\n");
+        addIndent(indent).append("}");
+        if (op.isBulkload()) {
+            buffer.append(",\n");
+            buffer.append("\"bulkload\":\"true\"");
+        }
+        return null;
+    }
+
+    public String getIndexOpString(Kind opKind) {
+        switch (opKind) {
+            case DELETE:
+                return "delete-from";
+            case INSERT:
+                return "insert-into";
+            case UPSERT:
+                return "upsert-into";
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitTokenizeOperator(TokenizeOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"tokenize\"");
+        variablePrintHelper(op.getTokenizeVars(), indent);
+        if (!op.getSecondaryKeyExpressions().isEmpty()) {
+            addIndent(0).append(",\n");
+            pprintExprList(op.getSecondaryKeyExpressions(), indent);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visitSinkOperator(SinkOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"sink\"");
+        return null;
+    }
+
+    @Override
+    public Void visitDelegateOperator(DelegateOperator op, Integer indent) throws AlgebricksException {
+        addIndent(indent).append("\"operator\":\"" + op.toString() + "\"");
+        return null;
+    }
+
+    protected AlgebricksAppendable addIndent(int level) throws AlgebricksException {
+        for (int i = 0; i < level; ++i) {
+            buffer.append(' ');
+        }
+        return buffer;
+    }
+
+    protected void printNestedPlans(AbstractOperatorWithNestedPlans op, Integer indent) throws AlgebricksException {
+        idCounter.nextPrefix();
+        buffer.append("[\n");
+        boolean first = true;
+        for (ILogicalPlan p : op.getNestedPlans()) {
+            if (!first) {
+                buffer.append(",");
+            }
+            printPlanJson(p, this, indent + 4);
+            first = false;
+
+        }
+        addIndent(indent).append("]");
+        idCounter.previousPrefix();
+    }
+
+    //Done--Look for exprRef
+    protected void pprintExprList(List<Mutable<ILogicalExpression>> expressions, Integer indent)
+            throws AlgebricksException {
+        addIndent(indent);
+        buffer.append("\"expressions\":\"");
+        boolean first = true;
+        for (Mutable<ILogicalExpression> exprRef : expressions) {
+            if (first) {
+                first = false;
+            } else {
+                buffer.append(", ");
+            }
+            buffer.append(exprRef.getValue().accept(exprVisitor, indent).replace('"', ' '));
+        }
+        buffer.append("\"");
+    }
+
+    protected void pprintVeList(List<Pair<LogicalVariable, Mutable<ILogicalExpression>>> vePairList, Integer indent)
+            throws AlgebricksException {
+        buffer.append("[");
+        boolean fst = true;
+        for (Pair<LogicalVariable, Mutable<ILogicalExpression>> ve : vePairList) {
+            if (fst) {
+                fst = false;
+            } else {
+                buffer.append(",");
+            }
+            if (ve.first != null) {
+                buffer.append("{\"variable\":\"" + ve.first.toString().replace('"', ' ') + "\"," + "\"expression\":\""
+                        + ve.second.toString().replace('"', ' ') + "\"}");
+            } else {
+                buffer.append("{\"expression\":\"" + ve.second.getValue().accept(exprVisitor, indent).replace('"', ' ')
+                        + "\"}");
+            }
+        }
+        buffer.append("]");
+    }
+
+}
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/PlanPrettyPrinter.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/PlanPrettyPrinter.java
index 9a55f11..cf99d3b 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/PlanPrettyPrinter.java
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/PlanPrettyPrinter.java
@@ -18,76 +18,42 @@
  */
 package org.apache.hyracks.algebricks.core.algebra.prettyprint;
 
-import org.apache.commons.lang3.mutable.Mutable;
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
-import org.apache.hyracks.algebricks.core.algebra.base.ILogicalOperator;
 import org.apache.hyracks.algebricks.core.algebra.base.ILogicalPlan;
-import org.apache.hyracks.algebricks.core.algebra.base.IPhysicalOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractLogicalOperator;
-import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractOperatorWithNestedPlans;
 
 public class PlanPrettyPrinter {
-    public static void printPlan(ILogicalPlan plan, LogicalOperatorPrettyPrintVisitor pvisitor, int indent)
-            throws AlgebricksException {
-        for (Mutable<ILogicalOperator> root : plan.getRoots()) {
-            printOperator((AbstractLogicalOperator) root.getValue(), pvisitor, indent);
+    @FunctionalInterface
+    public interface print<T1, T2, T3> {
+        void apply(T1 arg1, T2 arg2, T3 arg3) throws AlgebricksException;
+    }
+
+    public static void printOperator(AbstractLogicalOperator op, LogicalOperatorPrettyPrintVisitor pvisitor,
+            int indent) throws AlgebricksException {
+        print<AbstractLogicalOperator, LogicalOperatorPrettyPrintVisitor, Integer> printOperator =
+                LogicalOperatorPrettyPrintVisitor::printOperator;
+        printOperator.apply(op, pvisitor, indent);
+    }
+
+    public static <T extends AbstractLogicalOperatorPrettyPrintVisitor> void printPlan(ILogicalPlan plan,
+            T pvisitor, int indent) throws AlgebricksException {
+        if (pvisitor.getClass().equals(LogicalOperatorPrettyPrintVisitor.class)) {
+            print<ILogicalPlan, LogicalOperatorPrettyPrintVisitor, Integer> printPlan =
+                    LogicalOperatorPrettyPrintVisitor::printPlan;
+            printPlan.apply(plan,(LogicalOperatorPrettyPrintVisitor) pvisitor, indent);
         }
+        else if (pvisitor.getClass().equals(LogicalOperatorPrettyPrintVisitorJson.class)) {
+            print<ILogicalPlan, LogicalOperatorPrettyPrintVisitorJson, Integer> printPlan =
+                    LogicalOperatorPrettyPrintVisitorJson::printPlanJson;
+            printPlan.apply(plan, (LogicalOperatorPrettyPrintVisitorJson)pvisitor, indent);
+        }
+
     }
 
     public static void printPhysicalOps(ILogicalPlan plan, AlgebricksAppendable out, int indent)
             throws AlgebricksException {
-        for (Mutable<ILogicalOperator> root : plan.getRoots()) {
-            printPhysicalOperator((AbstractLogicalOperator) root.getValue(), indent, out);
-        }
-    }
-
-    public static void printOperator(AbstractLogicalOperator op, LogicalOperatorPrettyPrintVisitor pvisitor, int indent)
-            throws AlgebricksException {
-        final AlgebricksAppendable out = pvisitor.get();
-        op.accept(pvisitor, indent);
-        IPhysicalOperator pOp = op.getPhysicalOperator();
-
-        if (pOp != null) {
-            out.append("\n");
-            pad(out, indent);
-            appendln(out, "-- " + pOp.toString() + "  |" + op.getExecutionMode() + "|");
-        } else {
-            appendln(out, " -- |" + op.getExecutionMode() + "|");
-        }
-
-        for (Mutable<ILogicalOperator> i : op.getInputs()) {
-            printOperator((AbstractLogicalOperator) i.getValue(), pvisitor, indent + 2);
-        }
-    }
-
-    private static void printPhysicalOperator(AbstractLogicalOperator op, int indent, AlgebricksAppendable out)
-            throws AlgebricksException {
-        IPhysicalOperator pOp = op.getPhysicalOperator();
-        pad(out, indent);
-        appendln(out, "-- " + pOp.toString() + "  |" + op.getExecutionMode() + "|");
-        if (op.hasNestedPlans()) {
-            AbstractOperatorWithNestedPlans opNest = (AbstractOperatorWithNestedPlans) op;
-            for (ILogicalPlan p : opNest.getNestedPlans()) {
-                pad(out, indent + 8);
-                appendln(out, "{");
-                printPhysicalOps(p, out, indent + 10);
-                pad(out, indent + 8);
-                appendln(out, "}");
-            }
-        }
-        for (Mutable<ILogicalOperator> i : op.getInputs()) {
-            printPhysicalOperator((AbstractLogicalOperator) i.getValue(), indent + 2, out);
-        }
-    }
-
-    private static void appendln(AlgebricksAppendable buf, String s) throws AlgebricksException {
-        buf.append(s);
-        buf.append("\n");
-    }
-
-    private static void pad(AlgebricksAppendable buf, int indent) throws AlgebricksException {
-        for (int i = 0; i < indent; ++i) {
-            buf.append(' ');
-        }
+        print<ILogicalPlan, AlgebricksAppendable, Integer> printOperator =
+                AbstractLogicalOperatorPrettyPrintVisitor::printPhysicalOps;
+        printOperator.apply(plan, out, indent);
     }
 }
diff --git a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/visitors/ILogicalOperatorVisitor.java b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/visitors/ILogicalOperatorVisitor.java
index 8da41e2..deb98b0 100644
--- a/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/visitors/ILogicalOperatorVisitor.java
+++ b/hyracks-fullstack/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/visitors/ILogicalOperatorVisitor.java
@@ -22,11 +22,11 @@
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.AggregateOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.AssignOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.DataSourceScanOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.DelegateOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.DistinctOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.DistributeResultOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.EmptyTupleSourceOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.ExchangeOperator;
-import org.apache.hyracks.algebricks.core.algebra.operators.logical.DelegateOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.GroupByOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.IndexInsertDeleteUpsertOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.InnerJoinOperator;