Package com.google.gxp.compiler.msgextract

Source Code of com.google.gxp.compiler.msgextract.MessageExtractor

/*
* 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.msgextract;

import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.gxp.compiler.alerts.AlertSetBuilder;
import com.google.gxp.compiler.alerts.AlertSink;
import com.google.gxp.compiler.alerts.SourcePosition;
import com.google.gxp.compiler.alerts.common.BadNodePlacementError;
import com.google.gxp.compiler.alerts.common.InvalidMessageError;
import com.google.gxp.compiler.base.CollapseExpression;
import com.google.gxp.compiler.base.Concatenation;
import com.google.gxp.compiler.base.ConvertibleToContent;
import com.google.gxp.compiler.base.DefaultingExpressionVisitor;
import com.google.gxp.compiler.base.ExampleExpression;
import com.google.gxp.compiler.base.ExhaustiveExpressionVisitor;
import com.google.gxp.compiler.base.Expression;
import com.google.gxp.compiler.base.ExtractedMessage;
import com.google.gxp.compiler.base.NoMessage;
import com.google.gxp.compiler.base.Node;
import com.google.gxp.compiler.base.OutputElement;
import com.google.gxp.compiler.base.PlaceholderEnd;
import com.google.gxp.compiler.base.PlaceholderNode;
import com.google.gxp.compiler.base.PlaceholderStart;
import com.google.gxp.compiler.base.Root;
import com.google.gxp.compiler.base.StringConstant;
import com.google.gxp.compiler.base.UnexpectedNodeException;
import com.google.gxp.compiler.base.UnextractedMessage;
import com.google.gxp.compiler.i18ncheck.I18nCheckedTree;
import com.google.transconsole.common.messages.InvalidMessageException;
import com.google.transconsole.common.messages.Message;
import com.google.transconsole.common.messages.MessageBuilder;

import java.util.List;

/**
* Replaces message elements and contained Output nodes with validated message
* objects that hold message data as well as a set of DynamicPlaceholder
* children. For example:
*
* <center><img src="http://go/gxpc.java/MessageExtraction.png"></center>
*/
public class MessageExtractor implements Function<I18nCheckedTree, MessageExtractedTree> {

  public MessageExtractedTree apply(I18nCheckedTree tree) {
    List<ExtractedMessage> messages = Lists.newArrayList();
    AlertSetBuilder alertSetBuilder = new AlertSetBuilder(tree.getAlerts());
    Root root = tree.getRoot().acceptVisitor(new OutsideMessageVisitor(alertSetBuilder, messages));

    return new MessageExtractedTree(tree.getSourcePosition(), alertSetBuilder.buildAndClear(),
                                    root, messages);
  }

  /**
   * Visitor used when we are <em>not</em> inside of a {@code <gxp:msg>} or
   * {@code <gxp:nomsg>} element.
   */
  private static class OutsideMessageVisitor extends ExhaustiveExpressionVisitor {
    private final AlertSink alertSink;
    private final List<ExtractedMessage> messages;

    OutsideMessageVisitor(AlertSink alertSink, List<ExtractedMessage> messages) {
      this.alertSink = Preconditions.checkNotNull(alertSink);
      this.messages = Preconditions.checkNotNull(messages);
    }

    public void addMessages(ExtractedMessage message) {
      messages.add(message);
    }

    @Override
    public Expression visitNoMessage(NoMessage noMsg) {
      InsideNoMessageVisitor insideNoMsgVisitor =
          new InsideNoMessageVisitor(alertSink, noMsg, this);
      return noMsg.getSubexpression().acceptVisitor(insideNoMsgVisitor);
    }

    @Override
    public Expression visitUnextractedMessage(UnextractedMessage msg) {
      InsideMessageVisitor subVisitor = new InsideMessageVisitor(alertSink, this, msg);
      msg.getContent().acceptVisitor(subVisitor);
      return subVisitor.getResult();
    }

    @Override
    public Expression visitPlaceholderNode(PlaceholderNode ph) {
      alertSink.add(new BadNodePlacementError(ph, null));
      return ph.getContent().acceptVisitor(this);
    }

    @Override
    public Expression visitPlaceholderStart(PlaceholderStart value) {
      throw new UnexpectedNodeException(value);
    }

    @Override
    public Expression visitPlaceholderEnd(PlaceholderEnd value) {
      throw new UnexpectedNodeException(value);
    }
  }

  /**
   * Visitor used when we are <em>not</em> inside of a {@code <gxp:msg>} or
   * {@code <gxp:nomsg>} element.
   */
  private static class InsideNoMessageVisitor extends ExhaustiveExpressionVisitor {
    private final AlertSink alertSink;
    private final NoMessage rootNoMsg;
    private final OutsideMessageVisitor outsideMessageVisitor;

    InsideNoMessageVisitor(AlertSink alertSink,
                           NoMessage rootNoMsg,
                           OutsideMessageVisitor outsideMessageVisitor) {
      this.alertSink = Preconditions.checkNotNull(alertSink);
      this.rootNoMsg = Preconditions.checkNotNull(rootNoMsg);
      this.outsideMessageVisitor = Preconditions.checkNotNull(outsideMessageVisitor);
    }

    @Override
    public Expression visitUnextractedMessage(UnextractedMessage msg) {
      alertSink.add(new BadNodePlacementError(msg, rootNoMsg));
      return msg.acceptVisitor(outsideMessageVisitor);
    }

    @Override
    public Expression visitNoMessage(NoMessage noMsg) {
      alertSink.add(new BadNodePlacementError(noMsg, rootNoMsg));
      return noMsg.getSubexpression().acceptVisitor(this);
    }

    @Override
    public Expression visitPlaceholderNode(PlaceholderNode ph) {
      alertSink.add(new BadNodePlacementError(ph, rootNoMsg));
      return ph.getContent().acceptVisitor(this);
    }

    @Override
    public Expression visitPlaceholderStart(PlaceholderStart value) {
      throw new UnexpectedNodeException(value);
    }

    @Override
    public Expression visitPlaceholderEnd(PlaceholderEnd value) {
      throw new UnexpectedNodeException(value);
    }
  }

  /**
   * Visitor used when we <em>are</em> inside of a {@code <gxp:msg>} element.
   * The calling protocol is to create the InsideMessageVisitor, have it visit
   * the content of an {@code UnextractedMessage} (which will cause to to
   * accumulate all of the information it needs) and then call the
   * {@code getResult} method, which will return an {@code ExtractedMessage},
   * or a stand-in if there was an error.
   */
  private static class InsideMessageVisitor extends DefaultingExpressionVisitor<Void> {
    private final AlertSink alertSink;
    private final OutsideMessageVisitor outsideMessageVisitor;
    private final UnextractedMessage msg;

    // Mutable state:
    private final MessageBuilder tcMessageBuilder = new MessageBuilder();
    private final List<Expression> parameters = Lists.newArrayList();
    private boolean invalid = false;

    /**
     * {@link com.google.i18n.Message} only supports the pattern "%[1-9%]" when
     * looking for dynamic placeholders, so only 9 dynamic placeholders are
     * allowed.
     */
    private static final int MAX_DYNAMIC_PLACEHOLDER_COUNT = 9;

    InsideMessageVisitor(AlertSink alertSink,
                         OutsideMessageVisitor outsideMessageVisitor,
                         UnextractedMessage msg) {
      this.alertSink = Preconditions.checkNotNull(alertSink);
      this.outsideMessageVisitor = Preconditions.checkNotNull(outsideMessageVisitor);
      this.msg = Preconditions.checkNotNull(msg);

      // Populate the tcMessageBuilder with a content-type
      tcMessageBuilder.setContentType(msg.getSchema().getCanonicalContentType());

      // Populate tcMessageBuilder with attributes from msg.
      // - The meaning is used to compute the msg id.
      if (null != msg.getMeaning()) {
        tcMessageBuilder.setMeaning(msg.getMeaning());
      }

      // mark this message as hidden if indicated
      if (msg.isHidden()) {
        tcMessageBuilder.setHidden(true);
      }

      // - Comments are notes for translators
      if (null != msg.getComment()) {
        tcMessageBuilder.setDescription(msg.getComment());
      }

      // - Include the source gxp file so that l10n experts for other projects
      //   can avoid clobbering this project's translated strings.
      SourcePosition source = msg.getSourcePosition();
      if (source != null) {
        tcMessageBuilder.addSource(source.getSource().toRelativeFilename()
                                   + ": L" + source.getLine()
                                   // For backwards compatibility with the old
                                   // compiler, report a 0-based column number.
                                   + ", C" + (source.getColumn() - 1));
      }
    }

    Expression getResult() {
      if (!invalid) {
        try {
          Message message = tcMessageBuilder.createMessage();
          ExtractedMessage emsg =
              new ExtractedMessage(msg, msg.getSchema(), msg.getName(), message, parameters);
          outsideMessageVisitor.addMessages(emsg);
          return emsg;
        } catch (InvalidMessageException imx) {
          recordInvalidMessageException(msg, imx);
        }
      }

      if (!invalid) {
        throw new AssertionError("Attempting to create stand-in when message is valid!");
      }
      // TODO(laurence): create a better stand-in
      return new StringConstant(msg, msg.getSchema(), "");
    }

    private void recordInvalidMessageException(Node node, InvalidMessageException imx) {
      alertSink.add(new InvalidMessageError(node.getSourcePosition(), imx));
      invalid = true;
    }

    @Override
    public Void visitStringConstant(StringConstant value) {
      tcMessageBuilder.appendText(value.evaluate());
      return null;
    }

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

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

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

    @Override
    protected Void defaultVisitExpression(Expression node) {
      alertSink.add(new BadNodePlacementError(node, msg));
      return null;
    }

    @Override
    public Void visitPlaceholderNode(PlaceholderNode ph) {
      StringBuilder sb = new StringBuilder();
      for (Expression subExpression : ph.getContent().separate()) {
        subExpression =
            subExpression.acceptVisitor(outsideMessageVisitor);
        if (subExpression.hasStaticString()) {
          String s = Preconditions.checkNotNull(subExpression.getStaticString(alertSink,
                                                                   null));
          sb.append(s.replace("%", "%%"));
        } else {
          int index;
          for (index = 0; index < parameters.size(); index++) {
            if (parameters.get(index).alwaysEquals(subExpression)) {
              break;
            }
          }

          // Placeholders parameters start at 1.
          int id = index + 1;
          if (id > MAX_DYNAMIC_PLACEHOLDER_COUNT) {
            alertSink.add(new TooManyDynamicPlaceholdersError(subExpression));
          } else {
            if (index == parameters.size()) {
              parameters.add(subExpression);
            }
            sb.append("%");
            sb.append(String.valueOf(id));
          }
        }
      }

      try {
        tcMessageBuilder.appendPlaceholder(sb.toString(),
                                           ph.getName().toUpperCase(),
                                           ph.getExample());
      } catch (InvalidMessageException imx) {
        recordInvalidMessageException(ph, imx);
      }
      return null;
    }

    @Override
    public Void visitOutputElement(OutputElement value) {
      throw new UnexpectedNodeException(value);
    }

    @Override
    public Void visitCollapseExpression(CollapseExpression value) {
      throw new UnexpectedNodeException(value);
    }

    @Override
    public Void visitExtractedMessage(ExtractedMessage value) {
      throw new UnexpectedNodeException(value);
    }

    @Override
    public Void visitPlaceholderStart(PlaceholderStart value) {
      throw new UnexpectedNodeException(value);
    }

    @Override
    public Void visitPlaceholderEnd(PlaceholderEnd value) {
      throw new UnexpectedNodeException(value);
    }
  }
}
TOP

Related Classes of com.google.gxp.compiler.msgextract.MessageExtractor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.