All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gxp.compiler.java..svn.text-base.JavaCodeGenerator.svn-base Maven / Gradle / Ivy

Go to download

Google XML Pages (GXP) is a templating system used to generate XML/SGML markup (most often HTML).

The newest version!
/*
 * Copyright (C) 2008 Google Inc.
 *
 * Licensed 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 com.google.gxp.compiler.java;

import static com.google.gxp.compiler.base.OutputLanguage.JAVA;

import com.google.common.base.CharEscapers;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gxp.compiler.alerts.AlertSink;
import com.google.gxp.compiler.alerts.SourcePosition;
import com.google.gxp.compiler.base.AbbrExpression;
import com.google.gxp.compiler.base.AttrBundleParam;
import com.google.gxp.compiler.base.AttrBundleReference;
import com.google.gxp.compiler.base.BooleanConstant;
import com.google.gxp.compiler.base.BoundCall;
import com.google.gxp.compiler.base.Call;
import com.google.gxp.compiler.base.CallVisitor;
import com.google.gxp.compiler.base.Callable;
import com.google.gxp.compiler.base.CallableVisitor;
import com.google.gxp.compiler.base.Concatenation;
import com.google.gxp.compiler.base.Conditional;
import com.google.gxp.compiler.base.ConstructedConstant;
import com.google.gxp.compiler.base.ConvertibleToContent;
import com.google.gxp.compiler.base.DefaultingExpressionVisitor;
import com.google.gxp.compiler.base.EscapeExpression;
import com.google.gxp.compiler.base.ExampleExpression;
import com.google.gxp.compiler.base.ExceptionExpression;
import com.google.gxp.compiler.base.ExhaustiveExpressionVisitor;
import com.google.gxp.compiler.base.Expression;
import com.google.gxp.compiler.base.ExpressionVisitor;
import com.google.gxp.compiler.base.ExtractedMessage;
import com.google.gxp.compiler.base.FormalParameter;
import com.google.gxp.compiler.base.Implementable;
import com.google.gxp.compiler.base.InstanceCallable;
import com.google.gxp.compiler.base.IsXmlExpression;
import com.google.gxp.compiler.base.JavaAnnotation;
import com.google.gxp.compiler.base.LoopExpression;
import com.google.gxp.compiler.base.NativeExpression;
import com.google.gxp.compiler.base.ObjectConstant;
import com.google.gxp.compiler.base.OutputLanguage;
import com.google.gxp.compiler.base.Parameter;
import com.google.gxp.compiler.base.StringConstant;
import com.google.gxp.compiler.base.Template;
import com.google.gxp.compiler.base.ThrowsDeclaration;
import com.google.gxp.compiler.base.UnboundCall;
import com.google.gxp.compiler.base.UnexpectedNodeException;
import com.google.gxp.compiler.base.ValidatedCall;
import com.google.gxp.compiler.codegen.DuplicateMessageNameError;
import com.google.gxp.compiler.codegen.LoopMissingBothIterableAndIteratorError;
import com.google.gxp.compiler.codegen.MissingExpressionError;
import com.google.gxp.compiler.msgextract.MessageExtractedTree;
import com.google.gxp.compiler.reparent.Attribute;
import com.google.gxp.compiler.schema.AttributeValidator;
import com.google.gxp.compiler.schema.ContentFamilyVisitor;
import com.google.gxp.compiler.schema.Schema;
import com.google.transconsole.common.messages.Message;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * A {@code CodeGenerator} that generates Java code.
 */
public class JavaCodeGenerator extends BaseJavaCodeGenerator {

  /**
   * @param tree the MessageExtractedTree to compile.
   * @param runtimeMessageSource the message source to use at runtime, or null
   * if none was provided. This is a (typically dotted) prefix used when
   * loading message resources at runtime.
   */
  public JavaCodeGenerator(MessageExtractedTree tree, String runtimeMessageSource) {
    super(tree, runtimeMessageSource);
  }

  @Override
  protected TemplateWorker createTemplateWorker(Appendable appendable,
                                                AlertSink alertSink,
                                                Template template,
                                                String runtimeMessageSource) {
    return new TemplateWorker(appendable, alertSink, template, runtimeMessageSource);
  }

  /**
   * Helper class which exists mainly so we don't have to pass the CIndenter
   * and AlertSink everywhere manually.
   */
  protected static class TemplateWorker extends BaseJavaCodeGenerator.TemplateWorker {
    private int varCounter = 0;
    private final String runtimeMessageSource;

    protected final Set anonymousSchemas = Sets.newTreeSet();

    TemplateWorker(Appendable appendable, AlertSink alertSink,
                   Template template, String runtimeMessageSource) {
      super(appendable, alertSink, template);
      this.runtimeMessageSource = runtimeMessageSource;
    }

    public TemplateWorker createSubWorker(Appendable newAppendable) {
      return new TemplateWorker(newAppendable, alertSink, template, runtimeMessageSource);
    }

    /**
     * Creates a unique (to this Worker) variable name.
     *
     * @param token string which is included in variable name. This can be used
     * to (slightly) increase readability of the generated code.
     */
    protected final String createVarName(String token) {
      return "gxp$" + token + "$" + varCounter++;
    }

    @Override
    protected String getBaseClassName() {
      return "com.google.gxp.base.GxpTemplate";
    }

    @Override
    protected void appendClass() {
      appendAnnotations(template.getJavaAnnotations(JavaAnnotation.Element.CLASS));
      appendClassDecl();
      appendLine();
      if (runtimeMessageSource != null) {
        formatLine("private static final String GXP$MESSAGE_SOURCE = %s;",
                   JAVA.toStringLiteral(runtimeMessageSource));
        appendNamedMessageDefinitions();
        appendLine();
      }
      appendWriteMethod();
      appendDefaultAccessors();
      appendParamConstructors();
      appendAnonymousInterfaces();
      appendGetArgListMethod();
      appendGetGxpClosureMethod(true);
      appendInterface();
      appendInstance();
      appendLine("}");
    }

    protected void appendClassDecl() {
      formatLine(template.getSourcePosition(), "public class %s extends %s {",
                 getClassName(template.getName()), getBaseClassName());
    }

    private void appendNamedMessageDefinitions() {
      template.getContent().acceptVisitor(new DuplicateMessageNameVisitor());
      template.getContent().acceptVisitor(new NamedExtractedMessageVisitor());
    }

    private String getAnonymousJavaType(Schema schema) {
      return "Anonymous" + getBaseName(schema.getJavaType());
    }

    /**
     * Generates private abstract static classes for all the anonymous closures
     * needed by this template
     */
    private void appendAnonymousInterfaces() {
      for (Schema schema : anonymousSchemas) {
        appendLine();
        appendLine("private abstract static class " + getAnonymousJavaType(schema));
        appendLine("    extends GxpTemplate.AnonymousGxpClosure");
        appendLine("    implements " + schema.getJavaType() + " {");
        appendLine("}");
      }
    }

    /**
     * Generates private constants for default values and accessor methods for
     * retrieving them.
     */
    private void appendDefaultAccessors() {
      for (Parameter param : template.getAllParameters()) {
        Expression defaultValue = param.getDefaultValue();
        if (defaultValue != null) {
          String paramType = toJavaType(param.getType());
          appendLine();
          formatLine(param.getDefaultValue().getSourcePosition(),
                     "private static final %s GXP_DEFAULT$%s = %s;",
                     paramType, param.getPrimaryName(),
                     getJavaExpression(param.getDefaultValue()));

          appendLine();
          String methodName = getDefaultMethodName(param);
          formatLine("public static %s %s() {", paramType, methodName);
          formatLine("return GXP_DEFAULT$%s;", param.getPrimaryName());
          appendLine("}");
        }
      }
    }

    /**
     * Generates methods for constructing object parameters from strings.
     */
    private void appendParamConstructors() {
      for (Parameter param : template.getParameters()) {
        if (param.getConstructor() != null) {
          appendLine();
          formatLine(param.getSourcePosition(),
                     "public static %s %s(String %s) {",
                     toJavaType(param.getType()),
                     getConstructorMethodName(param),
                     param.getPrimaryName());
          appendLine(param.getSourcePosition(), "return "
                     + param.getConstructor().acceptVisitor(toExpressionVisitor) + ";");
          appendLine("}");
        }
      }
    }

    private List getCallArguments(Callable callee, Map callerAttrs) {
      List fParams = Lists.newArrayList();
      String calleeName = getCalleeName(callee);
      for (FormalParameter parameter : callee.getParameters()) {
        String paramName = parameter.getPrimaryName();
        if (!Implementable.INSTANCE_PARAM_NAME.equals(paramName)) {
          Attribute value = callerAttrs.get(paramName);

          String defaultFetchString = (parameter.getType().getDefaultValue() == null)
              ? calleeName + "." + getDefaultMethodName(parameter) + "()"
              : getJavaExpression(parameter.getType().getDefaultValue());

          if (value == null) {
            fParams.add(defaultFetchString);
          } else if (value.getCondition() != null) {
            String s = "(" +
                getJavaExpression(value.getCondition()) +
                " ? " + getJavaExpression(value.getValue()) + " : " +
                defaultFetchString + ")";
            fParams.add(s);
          } else {
            fParams.add(getJavaExpression(value.getValue()));
          }
        }
      }
      return fParams;
    }

    @Override
    protected void appendWriteMethodBody() {
      appendLine("final java.util.Locale gxp_locale = gxp_context.getLocale();");
      List throwsDecls = template.getThrowsDeclarations();
      if (!throwsDecls.isEmpty()) {
        appendLine("try {");
      }

      template.getContent().acceptVisitor(statementVisitor);

      if (!throwsDecls.isEmpty()) {
        String runtimeExceptionVar = createVarName("runtimeException");
        String causeVar = createVarName("cause");
        formatLine("} catch (com.google.gxp.base.GxpRuntimeException %s) {", runtimeExceptionVar);
        formatLine("final java.lang.Throwable %s = %s.getCause();", causeVar, runtimeExceptionVar);
        for (ThrowsDeclaration throwsDecl : throwsDecls) {
          String excType = throwsDecl.getExceptionType();
          formatLine(throwsDecl.getSourcePosition(),
                     "if (%s instanceof %s) throw (%s) %s;",
                     causeVar, excType, excType, causeVar);
        }
        formatLine("throw %s;", runtimeExceptionVar);
        appendLine("}");
      }
    }

    protected void writeExpression(SourcePosition sourcePosition, String expr) {
      appendLine(sourcePosition, GXP_OUT_VAR + ".append(" + expr + ");");
    }

    private static final int MAX_JAVA_STRING_LENGTH = 65534;

    // TODO(danignatoff): something else does this too. Merge.
    protected void writeString(SourcePosition pos, String s) {
      int length = s.length();
      if (length != 0) {
        int curPos = 0;
        while (length - curPos > MAX_JAVA_STRING_LENGTH) {
          writeExpression(pos, JAVA.toStringLiteral(
                              s.substring(curPos, curPos + MAX_JAVA_STRING_LENGTH)));
          curPos += MAX_JAVA_STRING_LENGTH;
        }
        writeExpression(pos, JAVA.toStringLiteral(s.substring(curPos, length)));
      }
    }

    private final StatementVisitor statementVisitor = getStatementVisitor();
    private final ExpressionVisitor toExpressionVisitor =
        new ToExpressionVisitor();

    private final ExpressionVisitor toEscapableExpressionVisitor =
        getToEscapableExpressionVisitor();

    protected StatementVisitor getStatementVisitor() {
      return new StatementVisitor();
    }

    protected final Deque instantiatedGxps = new ArrayDeque();

    /**
     * @return either the fully qualified static name of the GXP if we're
     * doing a static call, or the variable name of the instantiated GXP
     * if we're doing an instance call.
     */
    protected String getCalleeName(Callable callee) {
      return callee.acceptCallableVisitor(new CallableVisitor() {
        public String visitCallable(Callable callable) {
          return callable.getName().toString();
        }

        public String visitInstanceCallable(InstanceCallable callable) {
          return instantiatedGxps.peek();
        }
      });
    }

    /**
     * A visitor that creates Alerts when it encounters a duplicate Java message name.
     */
    private class DuplicateMessageNameVisitor extends ExhaustiveExpressionVisitor {

      private Set names = Sets.newHashSet();

      @Override
      public Expression visitExtractedMessage(ExtractedMessage msg) {
        if (isNamed(msg)) {
          String name = msg.getName(OutputLanguage.JAVA);
          if (names.contains(name)) {
            alertSink.add(new DuplicateMessageNameError(msg, name));
          } else {
            names.add(name);
          }
        }
        return super.visitExtractedMessage(msg);
      }
    }

    /**
     * A visitor that outputs MessageReference definitions to {@code out} based on the named
     * ExtractedMessages that it visits.
     */
    private class NamedExtractedMessageVisitor extends ExhaustiveExpressionVisitor {

      private static final String MESSAGE_REFERENCE_CLASSNAME_PREFIX
          = "com.google.gxp.base.MessageReference";

      @Override
      public Expression visitExtractedMessage(ExtractedMessage msg) {
        if (isNamed(msg)) {
          validateName(msg);
          outputNamedMessage(msg);
        }
        return super.visitExtractedMessage(msg);
      }

      private void validateName(ExtractedMessage msg) {
        JAVA.validateName(alertSink, msg, msg.getName(OutputLanguage.JAVA));
      }

      private void outputNamedMessage(ExtractedMessage msg) {
        // TODO: include msg.getParameters().size() in class name?
        formatLine(msg.getSourcePosition(), "public static final %s %s",
                   MESSAGE_REFERENCE_CLASSNAME_PREFIX,
                   msg.getName(OutputLanguage.JAVA));
        formatLine("    = new %s(GXP$MESSAGE_SOURCE, %sL);",
                   MESSAGE_REFERENCE_CLASSNAME_PREFIX,
                   msg.getTcMessage().getId());
      }
    }

    private boolean isNamed(ExtractedMessage msg) {
      String name = msg.getName(OutputLanguage.JAVA);
      return name != null && name.trim().length() > 0;
    }

    /**
     * A visitor that outputs statements to {@code out} based on the nodes that
     * it visits.
     */
    protected class StatementVisitor extends DefaultingExpressionVisitor
        implements CallVisitor {

      @Override
      public Void defaultVisitExpression(Expression node) {
        throw new UnexpectedNodeException(node);
      }

      @Override
      public Void visitExceptionExpression(ExceptionExpression value) {
        String excClass;
        switch (value.getKind()) {
          case NOT_SUPPORTED_IN_SGML_MODE:
            excClass = "java.lang.IllegalStateException";
            break;
          default:
            throw new AssertionError("Unsupported ExceptionExpression.Kind: " + value.getKind());
        }
        appendLine(value.getSourcePosition(),
                   "throw new " + excClass + "("
                   + JAVA.toStringLiteral(value.getMessage()) + ");");
        return null;
      }

      @Override
      public Void visitStringConstant(StringConstant value) {
        if (value.getSchema() == null) {
          throw new AssertionError();
        }
        writeString(value.getSourcePosition(), value.evaluate());
        return null;
      }

      @Override
      public Void visitEscapeExpression(EscapeExpression value) {
        formatLine(value.getSourcePosition(), "%s.INSTANCE.append(%s, %s, %s);",
                   value.getSchema().getJavaAppender(), GXP_OUT_VAR, GXP_CONTEXT_VAR,
                   getEscapableExpression(value.getSubexpression()));
        return null;
      }

      @Override
      public Void visitExampleExpression(ExampleExpression value) {
        return value.getSubexpression().acceptVisitor(this);
      }

      @Override
      public Void visitConvertibleToContent(ConvertibleToContent value) {
        value.getSubexpression().acceptVisitor(this);
        return null;
      }

      @Override
      public Void visitConcatenation(Concatenation value) {
        for (Expression subValue : value.getValues()) {
          subValue.acceptVisitor(this);
        }
        return null;
      }

      @Override
      public Void visitConditional(Conditional value) {
        Iterator clauses = value.getClauses().iterator();
        if (clauses.hasNext()) {
          appendIf("if (", clauses.next());
          while (clauses.hasNext()) {
            appendIf("} else if (", clauses.next());
          }
          Expression elseExpression = value.getElseExpression();
          if (!elseExpression.alwaysEmpty()) {
            appendLine("} else {");
            elseExpression.acceptVisitor(this);
          }
          appendLine("}");
        } else {
          throw new AssertionError("No clauses in Conditional!");
        }
        return null;
      }

      private void appendIf(String prefix, Conditional.Clause clause) {
        Expression predicate = clause.getPredicate();
        appendLine(predicate.getSourcePosition(),
                   prefix + getJavaExpression(predicate) + ") {");
        clause.getExpression().acceptVisitor(this);
      }

      private void writeConditionalDelim(Expression delimiter,
                                         String boolVar) {
        if (!delimiter.alwaysEmpty()) {
          formatLine("if (%s) {", boolVar);
          delimiter.acceptVisitor(this);
          appendLine("} else {");
          formatLine("%s = true;", boolVar);
          appendLine("}");
        }
      }

      @Override
      public Void visitLoopExpression(LoopExpression loop) {
        // start outer scope for temporary variables
        appendLine("{");
        Expression delimiter = loop.getDelimiter();
        String boolVar = createVarName("bool");
        if (!delimiter.alwaysEmpty()) {
          formatLine("boolean %s = false;", boolVar);
        }
        String tmpKeyVar = null, keyVar = null;
        if (loop.getKey() != null) {
          tmpKeyVar = createVarName("key");
          keyVar = JAVA.validateName(alertSink, loop, loop.getKey());
          formatLine("int %s = 0;", tmpKeyVar);
        }
        if (loop.getIterator() != null && loop.getIterator().canEvaluateAs(JAVA)) {
          String iterVar = createVarName("iter");
          Expression iter = loop.getIterator();
          formatLine(iter.getSourcePosition(),
                     "final java.util.Iterator %s = %s;",
                     JavaUtil.toReferenceType(toJavaType(loop.getType())),
                     iterVar,
                     getJavaExpression(iter));

          // the loop itself
          formatLine(loop.getSourcePosition(),
                     "while (%s.hasNext()) {",
                     iterVar);
          String itemExpr = JavaUtil.unbox(iterVar + ".next()",
                                           toJavaType(loop.getType()));
          formatLine(loop.getType().getSourcePosition(),
                     "final %s %s = %s;",
                     toJavaType(loop.getType()),
                     JAVA.validateName(alertSink, loop, loop.getVar()),
                     itemExpr);
          writeConditionalDelim(delimiter, boolVar);
          if (keyVar != null) {
            formatLine("final int %s = %s++;", keyVar, tmpKeyVar);
          }
          loop.getSubexpression().acceptVisitor(this);
          appendLine("}");
        } else if (loop.getIterable() != null && loop.getIterable().canEvaluateAs(JAVA)) {
          formatLine(loop.getSourcePosition(), "for (final %s %s : %s) {",
                     toJavaType(loop.getType()),
                     JAVA.validateName(alertSink, loop, loop.getVar()),
                     getJavaExpression(loop.getIterable()));
          writeConditionalDelim(delimiter, boolVar);
          if (keyVar != null) {
            formatLine("final int %s = %s++;", keyVar, tmpKeyVar);
          }
          loop.getSubexpression().acceptVisitor(this);
          appendLine("}");
        } else {
          // if we only have an expression for 1 of iterable/iterator then we
          // add a MissingExpressionAlert
          if (loop.getIterable() == null && loop.getIterator() != null) {
            alertSink.add(new MissingExpressionError(loop.getIterator(), JAVA));
          } else if (loop.getIterator() == null && loop.getIterable() != null) {
            alertSink.add(new MissingExpressionError(loop.getIterable(), JAVA));
          } else {
            alertSink.add(new LoopMissingBothIterableAndIteratorError(loop, JAVA));
          }
        }
        // close outer scope
        appendLine("}");
        return null;
      }

      @Override
      public Void visitAbbrExpression(AbbrExpression abbr) {
        appendLine("{");
        formatLine(abbr.getSourcePosition(),
                   "final %s %s = %s;",
                   toJavaType(abbr.getType()),
                   JAVA.validateName(alertSink, abbr, abbr.getName()),
                   getJavaExpression(abbr.getValue()));
        abbr.getContent().acceptVisitor(this);
        appendLine("}");
        return null;
      }

      @Override
      public Void visitCall(Call value) {
        return value.acceptCallVisitor(this);
      }

      public Void visitUnboundCall(UnboundCall call) {
        throw new UnexpectedNodeException(call);
      }

      public Void visitBoundCall(BoundCall call) {
        throw new UnexpectedNodeException(call);
      }

      public Void visitValidatedCall(final ValidatedCall call) {
        final Callable callee = call.getCallee();
        final Map params = call.getAttributes();
        final StringBuilder sb = new StringBuilder();

        boolean isInstance = callee.acceptCallableVisitor(new CallableVisitor() {
          public Boolean visitCallable(Callable callable) {
            return false;
          }

          public Boolean visitInstanceCallable(InstanceCallable callable) {
            instantiatedGxps.push(createVarName("inst"));
            Attribute thisAttr = params.get(Implementable.INSTANCE_PARAM_NAME);

            appendLine("{");
            if (thisAttr != null) {
              formatLine(call.getSourcePosition(), "%s %s = %s;",
                         toJavaType(callable.getInstanceType()),
                         instantiatedGxps.peek(),
                         thisAttr.getValue().acceptVisitor(toExpressionVisitor));
            }
            return true;
          }
        });

        sb.append(getCalleeName(callee));
        sb.append(".write(");
        sb.append(GXP_OUT_VAR);
        sb.append(", ");
        sb.append(GXP_CONTEXT_VAR);
        for (String param : getCallArguments(callee, params)) {
          sb.append(", ");
          sb.append(param);
        }
        sb.append(");");
        appendLine(call.getSourcePosition(), sb);

        if (isInstance) {
          instantiatedGxps.pop();
          appendLine("}");
        }

        // TODO(laurence): use mangled method name for greater safety?

        return null;
      }

      @Override
      public Void visitExtractedMessage(ExtractedMessage msg) {
        if (runtimeMessageSource == null) {
          alertSink.add(new NoMessageSourceError(msg));
        }
        Message tcMessage = msg.getTcMessage();
        formatLine("// MSG %s=%s",
                   tcMessage.getId(),
                   CharEscapers.javaStringUnicodeEscaper().escape(
                       tcMessage.getOriginal().replace("\n", " ")));

        StringBuilder sb = new StringBuilder("GxpTemplate.getMessage(GXP$MESSAGE_SOURCE, ");
        sb.append("gxp_context.getLocale(), ");
        sb.append(tcMessage.getId());
        sb.append("L");
        for (Expression param : msg.getParameters()) {
          sb.append(", ");
          sb.append(getEscapedString(param));
        }
        sb.append(")");
        String getMessage = sb.toString();

        writeExpression(msg.getSourcePosition(), getMessage);
        return null;
      }
    }

    protected final String getJavaExpression(Expression value) {
      return value.acceptVisitor(toExpressionVisitor);
    }

    protected final String getEscapedString(Expression value) {
      StringBuilder sb = new StringBuilder(value.getSchema().getJavaAppender());
      sb.append(".INSTANCE.append(new java.lang.StringBuilder(), ");
      sb.append(GXP_CONTEXT_VAR);
      sb.append(", ");
      sb.append(getJavaExpression(value));
      sb.append(").toString()");
      return sb.toString();
    }

    private String getEscapableExpression(Expression value) {
      return value.acceptVisitor(toEscapableExpressionVisitor);
    }

    protected ToEscapableExpressionVisitor getToEscapableExpressionVisitor() {
      return new ToEscapableExpressionVisitor();
    }

    /**
     * Converts an Expression into a Java expression that evaluates to a String
     * containing that Expression's value. This differs from
     * ToExpressionVisitor in that ToExpressionVisitor will sometimes return
     * closure types (eg: HtmlClosure), rather than Strings. Note that this is
     * only ever called on Expressions that are the child of an
     * EscapeExpression, so only types that can appear as the child of an
     * EscapeExpression need to be handled.
     */
    protected class ToEscapableExpressionVisitor
        extends DefaultingExpressionVisitor
        implements CallVisitor {

      @Override
      public String defaultVisitExpression(Expression value) {
        throw new UnexpectedNodeException(value);
      }

      @Override
      public String visitAttrBundleReference(AttrBundleReference value) {
        return value.getName();
      }

      @Override
      public String visitNativeExpression(NativeExpression value) {
        StringBuilder sb = new StringBuilder();
        sb.append('(');
        sb.append(JAVA.validateExpression(alertSink, value));
        sb.append(')');
        return sb.toString();
      }

      @Override
      public String visitEscapeExpression(EscapeExpression value) {
        StringBuilder sb = new StringBuilder();
        sb.append(value.getSchema().getJavaAppender());
        sb.append(".INSTANCE.append(new StringBuilder(), ");
        sb.append(GXP_CONTEXT_VAR);
        sb.append(", ");
        sb.append(getEscapableExpression(value.getSubexpression()));
        sb.append(")");

        return sb.toString();
      }

      @Override
      public String visitExtractedMessage(ExtractedMessage msg) {
        if (runtimeMessageSource == null) {
          alertSink.add(new NoMessageSourceError(msg));
        }
        Message tcMessage = msg.getTcMessage();
        StringBuilder sb = new StringBuilder("GxpTemplate.getMessage(GXP$MESSAGE_SOURCE, ");
        sb.append("gxp_context.getLocale(), ");
        sb.append(String.format("/* \"%s\" */ ",
                                CharEscapers.javaStringUnicodeEscaper().escape(
                                    tcMessage.getOriginal()
                                      .replace("\n", " ")
                                      .replace("\\*/", "*/"))));
        sb.append(tcMessage.getId());
        sb.append("L");
        for (Expression param : msg.getParameters()) {
          sb.append(", ");
          sb.append(getEscapedString(param));
        }
        sb.append(")");
        return sb.toString();
      }

      @Override
      public String visitCall(Call call) {
        return call.acceptCallVisitor(this);
      }

      @Override
      public String visitBoundCall(BoundCall call) {
        throw new UnexpectedNodeException(call);
      }

      @Override
      public String visitUnboundCall(UnboundCall call) {
        throw new UnexpectedNodeException(call);
      }

      @Override
      public String visitValidatedCall(ValidatedCall call) {
        StringBuilder sb = new StringBuilder(GXP_CONTEXT_VAR);
        sb.append(".getString(");
        sb.append(getJavaExpression(call));
        sb.append(")");
        return sb.toString();
      }
    }

    private class ToExpressionVisitor
        extends DefaultingExpressionVisitor
        implements CallVisitor {

      @Override
      public String defaultVisitExpression(Expression value) {
        throw new UnexpectedNodeException(value);
      }

      @Override
      public String visitAttrBundleParam(AttrBundleParam bundle) {
        // optimization where a single bundle is being passed without
        // restrictions to another.  Just pass the bundle along without
        // using a GxpAttrBundle.Builder
        if (bundle.getIncludeAttrs().isEmpty() && bundle.getAttrs().isEmpty()
            && bundle.getSubBundles().size() == 1) {
          return bundle.getSubBundles().get(0);
        }

        StringBuilder sb = new StringBuilder("new GxpAttrBundle.Builder<");
        sb.append(bundle.getSchema().getJavaType());
        sb.append(">(");
        List includeAttrs = Lists.newArrayList();
        for (String includeAttr : bundle.getIncludeAttrs()) {
          includeAttrs.add(JAVA.toStringLiteral(includeAttr));
        }
        COMMA_JOINER.appendTo(sb, includeAttrs);
        sb.append(')');
        for (Map.Entry entry : bundle.getAttrs().entrySet()) {
          AttributeValidator validator = entry.getKey();
          Expression condition = entry.getValue().getCondition();
          Expression value = entry.getValue().getValue();

          sb.append(".attr(");
          sb.append(JAVA.toStringLiteral(validator.getName()));
          sb.append(", ");
          sb.append(validator.isFlagSet(AttributeValidator.Flag.BOOLEAN)
                      ? value.acceptVisitor(this)
                      : toAnonymousClosure(value));
          if (condition != null) {
            sb.append(", ");
            sb.append(condition.acceptVisitor(this));
          }
          sb.append(")");
        }
        for (String subBundle : bundle.getSubBundles()) {
          sb.append(".addBundle(");
          sb.append(subBundle);
          sb.append(")");
        }
        sb.append(".build()");
        return sb.toString();
      }

      @Override
      public String visitBooleanConstant(BooleanConstant value) {
        return value.getValue().toString();
      }

      @Override
      public String visitStringConstant(StringConstant value) {
        if (value.getSchema() == null) {
          throw new AssertionError();
        }
        return value.getSchema().getContentFamily().acceptVisitor(
            STRING_CONSTANT_VISITOR, value);
      }

      private ContentFamilyVisitor
        STRING_CONSTANT_VISITOR =
        new ContentFamilyVisitor() {
          public String visitCss(StringConstant value) {
            return toAnonymousClosure(value);
          }

          public String visitJavaScript(StringConstant value) {
            return toAnonymousClosure(value);
          }

          public String visitMarkup(StringConstant value) {
            return toAnonymousClosure(value);
          }

          public String visitPlaintext(StringConstant value) {
            String s = CharEscapers.javaStringEscaper().escape(
                value.evaluate());
            return "\"" + s + "\"";
          }
        };

      // TODO(harryh): validate the value when it is a primitive type
      @Override
      public String visitObjectConstant(ObjectConstant node) {
        String value = node.getValue();
        String type = toJavaType(node.getType());
        if (!JavaUtil.isPrimitiveType(type)) {
          value = type + ".valueOf(\""
              + CharEscapers.javaStringEscaper().escape(value) + "\")";
        } else {
          if (!JavaUtil.isValidPrimitive(value, type)) {
            alertSink.add(new IllegalJavaPrimitiveError(node, value, type));
          }
        }

        return value;
      }

      @Override
      public String visitConstructedConstant(ConstructedConstant node) {
        StringBuilder sb = new StringBuilder();
        sb.append(getCalleeName(node.getCallee()));
        sb.append(".");
        sb.append(getConstructorMethodName(node.getParam()));
        sb.append("(\"");
        sb.append(node.getValue());
        sb.append("\")");

        return sb.toString();
      }

      @Override
      public String visitEscapeExpression(EscapeExpression value) {
        // TODO(laurence): perhaps we shouldn't be generating these
        // EscapeExpressions at all?
        return value.getSubexpression().acceptVisitor(this);
      }

      @Override
      public String visitExampleExpression(ExampleExpression value) {
        return value.getSubexpression().acceptVisitor(this);
      }

      @Override
      public String visitNativeExpression(NativeExpression value) {
        return JAVA.validateExpression(alertSink, value);
      }

      @Override
      public String visitConvertibleToContent(ConvertibleToContent value) {
        return toAnonymousClosure(value);
      }

      @Override
      public String visitConcatenation(Concatenation value) {
        return toAnonymousClosure(value);
      }

      @Override
      public String visitConditional(Conditional value) {
        // TODO(laurence): use "?:" operator instead?
        return toAnonymousClosure(value);
      }

      @Override
      public String visitLoopExpression(LoopExpression value) {
        return toAnonymousClosure(value);
      }

      @Override
      public String visitCall(Call value) {
        return value.acceptCallVisitor(this);
      }

      public String visitBoundCall(BoundCall call) {
        throw new UnexpectedNodeException(call);
      }

      public String visitUnboundCall(UnboundCall call) {
        throw new UnexpectedNodeException(call);
      }

      public String visitValidatedCall(final ValidatedCall call) {
        final Callable callee = call.getCallee();
        final StringBuilder sb = new StringBuilder();

        callee.acceptCallableVisitor(new CallableVisitor() {
          public Void visitCallable(Callable callable) {
            sb.append(callee.getName().toString());
            sb.append(".getGxpClosure(");
            COMMA_JOINER.appendTo(sb, getCallArguments(callee, call.getAttributes()));
            sb.append(")");
            return null;
          }

          public Void visitInstanceCallable(InstanceCallable callable) {
            // we can't use getGxpClosure in the instance call case, because we have to
            // return a single expression, so we can't set an inst variable like we
            // do in StatementVisitor#visitValidatedCall
            sb.append(toAnonymousClosure(call));
            return null;
          }
        });

        return sb.toString();
      }

      @Override
      public String visitAbbrExpression(AbbrExpression value) {
        return toAnonymousClosure(value);
      }

      @Override
      public String visitExtractedMessage(ExtractedMessage msg) {
        return toAnonymousClosure(msg);
      }

      @Override
      public String visitIsXmlExpression(IsXmlExpression ixe) {
        return "gxp_context.isForcingXmlSyntax()";
      }
    }

    private String toAnonymousClosure(Expression value) {
      StringBuilder sb = new StringBuilder();

      TemplateWorker subWorker = createSubWorker(sb);
      subWorker.toAnonymousClosureImpl(value);
      anonymousSchemas.addAll(subWorker.anonymousSchemas);

      // XXX We do this here to avoid the newline appended by CIndenter.append.
      sb.append("}");
      return sb.toString();
    }

    private void toAnonymousClosureImpl(Expression value) {
      // add the schema of this value to the Set of schemas we need
      // interface definitions for
      anonymousSchemas.add(value.getSchema());

      appendLine(value.getSourcePosition(),
                 "new " + getAnonymousJavaType(value.getSchema()) + "() {");

      StringBuilder sb = new StringBuilder();
      appendJavaThrowsDeclaration(sb, template.getThrowsDeclarations());
      appendLine("@Override");
      formatLine(value.getSourcePosition(),
                 "protected void writeImpl(%s) %s {",
                 GXP_SIG, sb.toString());

      // Fill in writeImpl method.
      value.acceptVisitor(statementVisitor);

      // End writeImpl method.
      appendLine("}");
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy