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

com.google.gxp.compiler.codegen..svn.text-base.OutputLanguageUtil.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.codegen;

import com.google.common.base.CharEscaper;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.gxp.compiler.alerts.AlertSink;
import com.google.gxp.compiler.base.NativeExpression;
import com.google.gxp.compiler.base.Node;
import com.google.gxp.compiler.base.OutputLanguage;

import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Base class for {@code OutputLanguage} utility classes. Contains
 * code common to all {@code OutputLanguages}.
 */
public abstract class OutputLanguageUtil {
  private final ImmutableSet reservedWords;
  private final ImmutableSet forbiddenOps;
  private final Pattern opsFinder;
  private final CharEscaper expressionEscaper;
  private final CharEscaper stringEscaper;

  protected OutputLanguageUtil(Iterable reservedWords,
                               Iterable forbiddenOps,
                               Pattern opsFinder,
                               CharEscaper expressionEscaper,
                               CharEscaper stringEscaper) {
    this.reservedWords = ImmutableSet.copyOf(reservedWords);
    this.forbiddenOps = ImmutableSet.copyOf(forbiddenOps);
    this.opsFinder = Preconditions.checkNotNull(opsFinder);
    this.expressionEscaper = Preconditions.checkNotNull(expressionEscaper);
    this.stringEscaper = Preconditions.checkNotNull(stringEscaper);
  }

  /**
   * Validate the given {@code NativeExpression} and add {@code Alert}s to the
   * {@code AlertSink} if necessary.
   */
  public String validateExpression(AlertSink alertSink, NativeExpression expr,
                                   OutputLanguage outputLanguage) {
    String result = expr.getNativeCode(outputLanguage);
    if (result == null) {
      alertSink.add(new MissingExpressionError(expr, outputLanguage));
      return "";
    }

    String s = removeCommentsAndLiterals(alertSink, expr, outputLanguage);

    Character c = findMismatches(s);
    if (c != null) {
      alertSink.add(new IllegalExpressionError(expr, outputLanguage));
    }

    Matcher m = opsFinder.matcher(s);
    while (m.find()) {
      if (forbiddenOps.contains(m.group())) {
        alertSink.add(new IllegalOperatorError(expr, outputLanguage, m.group()));
      }
    }

    return expressionEscaper.escape(result);
  }

  // the following regex has 4 pieces, one for each of the "tricky" tokens
  // we're trying to collapse. Each of these in turn has two groups. The
  // first is the entire token. The second is the terminator part of the
  // token. The existence of the first group of a piece can be used to
  // determine the token type of a match, and the emptiness of the second
  // piece can be used to determine if the token is unterminated.

  // TODO(jjb):    make java regex's suck less so that our code doesn't
  // TODO(madbot): have to be $%^&*(ing incomprehensible modem line noise

  private static final Pattern TRICKY_JAVA_TOKEN = Pattern.compile(
      "('(?:[^\\n'\\\\]|\\\\.)*(')?)"          // char literals
      + "|(\"(?:[^\\n\"\\\\]|\\\\.)*(\")?)"    // string literals
      + "|(/\\*(?:[^*]|\\*+[^/*])*(\\*/)?)"    // multi-line comments
      + "|(//[^\\n]*(\\n)?)", Pattern.DOTALL); // single-line comments


  /**
   * Remove comments, string literals, and character literals from the
   * input string returning what's left.
   *
   * Add alerts to the sink if there are unclosed comments or literals
   */
  private String removeCommentsAndLiterals(AlertSink alertSink,
                                           NativeExpression expr,
                                           OutputLanguage outputLanguage) {
    String str = expr.getNativeCode(outputLanguage);

    StringBuilder sb = new StringBuilder();
    int start = 0;

    Matcher m = TRICKY_JAVA_TOKEN.matcher(str);
    while (m.find()) {
      sb.append(str.substring(start, m.start()));
      start = m.end();
      if (m.group(1) != null) {
        sb.append("'x'");
        if (m.group(2) == null) {
          alertSink.add(new IllegalExpressionError(expr, outputLanguage));
        }
      } else if (m.group(3) != null) {
        sb.append("\"\"");
        if (m.group(4) == null) {
          alertSink.add(new IllegalExpressionError(expr, outputLanguage));
        }
      } else if (m.group(5) != null && m.group(6) == null) {
        alertSink.add(new IllegalExpressionError(expr, outputLanguage));
      } else if (m.group(7) != null && m.group(8) == null) {
        alertSink.add(new IllegalExpressionError(expr, outputLanguage));
      }
    }
    sb.append(str.substring(start));
    return sb.toString();
  }

  private static final Map NESTING_PAIRS
    = ImmutableMap.builder()
        .put('(', ')')
        .put('[', ']')
        .put('{', '}')
        .build();

  private static final Collection NESTING_OPENINGS = NESTING_PAIRS.keySet();
  private static final Collection NESTING_CLOSINGS = NESTING_PAIRS.values();

  /**
   * looks for mismatched ()s, []s, and {}s
   *
   * @reurn Character representing the mismatched item, or null if there
   *        are no mismatches
   */
  private Character findMismatches(String s) {
    Deque state = new ArrayDeque();
    for (Character c : s.toCharArray()) {
      if (NESTING_OPENINGS.contains(c)) {
        state.push(NESTING_PAIRS.get(c));
      } else if (NESTING_CLOSINGS.contains(c)) {
        if (state.isEmpty() || !state.peek().equals(c)) {
          return c;
        }
        state.pop();
      }
    }
    if (!state.isEmpty()) {
      return state.pop();
    }

    return null;
  }

  // compile all the patterns into a giant or Expression;
  protected static Pattern compileUnionPattern(String... patterns) {
    return Pattern.compile(Joiner.on("|").join(patterns));
  }

  /**
   * Validate that the given name is a valid variable name.  Add an
   * {@code Alert} to the {@code AlertSink} if it isn't.
   *
   * @return the name
   */
  public String validateName(AlertSink alertSink, Node node, String name,
                             OutputLanguage outputLanguage) {
    if (reservedWords.contains(name)) {
      alertSink.add(new IllegalNameError(node, outputLanguage, name));
    }
    return name;
  }

  /**
   * Convert a string to a string literal for the given
   * {@code OutputLanguage}.
   */
  public String toStringLiteral(String s) {
    return '"' + stringEscaper.escape(s) + '"';
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy