Package org.auraframework.impl.util

Source Code of org.auraframework.impl.util.TextTokenizer$Token

/*
* Copyright (C) 2013 salesforce.com, 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 org.auraframework.impl.util;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.auraframework.def.AttributeDef;
import org.auraframework.def.BaseComponentDef;
import org.auraframework.def.BaseComponentDef.WhitespaceBehavior;
import org.auraframework.def.ComponentDef;
import org.auraframework.def.ComponentDefRef;
import org.auraframework.def.DefDescriptor;
import org.auraframework.expression.Expression;
import org.auraframework.expression.PropertyReference;
import org.auraframework.impl.AuraImpl;
import org.auraframework.impl.expression.PropertyReferenceImpl;
import org.auraframework.impl.root.AttributeDefRefImpl;
import org.auraframework.impl.root.component.ComponentDefRefImpl;
import org.auraframework.impl.root.parser.handler.ExpressionContainerHandler;
import org.auraframework.impl.system.DefDescriptorImpl;
import org.auraframework.system.Location;
import org.auraframework.throwable.quickfix.AuraValidationException;
import org.auraframework.throwable.quickfix.InvalidExpressionException;
import org.auraframework.util.AuraTextUtil;

import com.google.common.collect.Sets;

/**
* Parses expressions and literal text. Those tokens can then be converted into
* the appropriate types of ComponentDefRefs or Strings
*/
public class TextTokenizer implements Iterable<TextTokenizer.Token> {
    public static final String BEGIN = "{!";
    public static final String END = "}";

    private static final Pattern EXPRESSION_PATTERN = Pattern.compile("(\\" + BEGIN + "[^}]+?\\" + END + ")",
            Pattern.DOTALL | Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);

    private static final Pattern EXPRESSION_UNWRAPPING_PATTERN = Pattern.compile("\\" + BEGIN + "(.*)\\" + END,
            Pattern.DOTALL | Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);

    private static final Pattern UNTERMINATED_EXPRESSION_PATTERN = Pattern.compile("(\\" + BEGIN + "[^}]+?)",
            Pattern.DOTALL | Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);

    private static final Pattern CURLY_BANG_INVERSION_PATTERN = Pattern.compile("(!\\{+" + "[^}]+?\\" + END + ")",
            Pattern.DOTALL | Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);

    private static final Pattern LABEL_GVP_PATTERN = Pattern.compile("(\\$Label\\.\\w+\\.\\w+)",
            Pattern.DOTALL | Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
   
    public static enum TokenType {
        PLAINTEXT(DefDescriptorImpl.getInstance("aura:text", ComponentDef.class)), EXPRESSION(DefDescriptorImpl
                .getInstance("aura:expression", ComponentDef.class));

        private final DefDescriptor<ComponentDef> componentDefDescriptor;

        private TokenType(DefDescriptor<ComponentDef> componentDefDescriptor) {
            this.componentDefDescriptor = componentDefDescriptor;
        }
    }

    private final List<Token> tokens = new ArrayList<Token>();
    private final Location location;
    private final String text;
    private final WhitespaceBehavior whitespaceBehavior;

    public static TextTokenizer tokenize(String value, Location location) throws AuraValidationException {
        return tokenize(value, location, BaseComponentDef.DefaultWhitespaceBehavior);
    }

    public static TextTokenizer tokenize(String value, Location location, WhitespaceBehavior whitespaceBehavior)
            throws AuraValidationException {
        TextTokenizer tokenizer = new TextTokenizer(value, location, whitespaceBehavior);
        tokenizer.doTokenize();
        return tokenizer;
    }

    private TextTokenizer(String text, Location location, WhitespaceBehavior whitespaceBehavior) {
        if (text != null && !text.isEmpty()) {
            String trimmedValue = text.trim();
            if (trimmedValue.isEmpty()) {
                text = " ";
            }
        }

        this.text = text;
        this.location = location;
        this.whitespaceBehavior = whitespaceBehavior;
    }

    /**
     * Parse the input text and represent it locally as a list of tokens. If a
     * {@link TokenType#PLAINTEXT} token looks like a malformed expression, then
     * throw a {@link org.auraframework.throwable.quickfix.QuickFixException}
     *
     * @throws AuraValidationException
     */
    private void doTokenize() throws AuraValidationException {
        if (!AuraTextUtil.isNullEmptyOrWhitespace(text)) {
            int lastMatch = 0;
            Matcher matcher = EXPRESSION_PATTERN.matcher(text);

            while (matcher.find()) {
                int start = matcher.start();
                int end = matcher.end();

                // All text before the currently found expression
                if (lastMatch != start) {
                    maybeAddPlainText(lastMatch, start);
                }

                tokens.add(new Token(TokenType.EXPRESSION, start, end));
                lastMatch = end;
            }

            // The remainder text after the last expression
            int length = text.length();
            if (lastMatch < length) {
                maybeAddPlainText(lastMatch, length);
            }
        } else if (text != null) {
            // Allow creation of whitespace or empty text components.
            // If the caller doesn't want this behavior, it should make that
            // decision externally before calling parse
            maybeAddPlainText(0, text.length());
        }
    }

    /**
     * This token is not a valid expression. If it looks like it wanted to be an
     * expression rather than real plain-text, then throw a validation error.
     *
     * @throws AuraValidationException if the plain text token looks like a
     *             malformed expression.
     */
    private void maybeAddPlainText(int begin, int end) throws AuraValidationException {
        String substring = text.substring(begin, end);

        Matcher unterminated = UNTERMINATED_EXPRESSION_PATTERN.matcher(substring);
        if (unterminated.matches()) {
            throw new InvalidExpressionException("Unterminated expression", location);
        }

        Matcher curlyBangInversion = CURLY_BANG_INVERSION_PATTERN.matcher(substring);
        if (curlyBangInversion.matches()) {
            throw new InvalidExpressionException("Found an expression starting with '!{' but it should be '{!'",
                    location);
        }

        Token token = new Token(TokenType.PLAINTEXT, begin, end);
    tokens.add(token);
    }

    public void addExpressionRefs(ExpressionContainerHandler handler) throws AuraValidationException {
        for (Token token : tokens) {
            token.createValue(handler);
        }
    }

    public List<ComponentDefRef> asComponentDefRefs(ExpressionContainerHandler cmpHandler)
            throws AuraValidationException {
        List<ComponentDefRef> ret = new ArrayList<ComponentDefRef>();
        for (Token token : tokens) {
            ComponentDefRef cdr = token.createComponentDefRef(cmpHandler);
            if (cdr != null) {
                ret.add(cdr);
            }
        }
        return ret;
    }

    /**
     * turns the tokens into raw strings or expressions, not componentdefrefs.
     * if there is only one, it returns that literal instead of a list. this is
     * used by various system tags
     */
    public Object asValue(ExpressionContainerHandler cmpHandler) throws AuraValidationException {
        if (tokens.isEmpty()) {
            return null;
        } else if (size() > 1) {
            throw new InvalidExpressionException(
                    "Cannot mix expression and literal string in attribute value, try rewriting like {!'foo' + v.bar}",
                    location);
        }
        return tokens.get(0).createValue(cmpHandler);
    }

    public int size() {
        return tokens.size();
    }

    /**
     * convenience method for removing curlybang.
     */
    public static String unwrap(String value) {
        Matcher matcher = EXPRESSION_UNWRAPPING_PATTERN.matcher(value);
        if (matcher.find()) {
            return matcher.group(1);
        } else {
            return value;
        }
    }

    @Override
    public Iterator<Token> iterator() {
        return AuraUtil.immutableList(tokens).iterator();
    }

    /**
     * inner class representing each token
     */
    public final class Token {

        private final TokenType type;
        private final int begin;
        private final int end;

        private Token(TokenType type, int begin, int end) {
            this.type = type;
            this.begin = begin;
            this.end = end;
            if (type == TokenType.PLAINTEXT) {
                // if «
            }
        }

        /**
         * creates either a string or expression object out of this token,
         * notifying the parent componentdef whenever an expression is found
         */
        private Object createValue(ExpressionContainerHandler cmpHandler) throws AuraValidationException {
          Object result;
            String raw = getRawValue();
            Set<PropertyReference> propRefs = null;
            if (type == TokenType.EXPRESSION) {
              propRefs = Sets.newHashSetWithExpectedSize(2);
                Expression e = AuraImpl.getExpressionAdapter().buildExpression(unwrap(raw), location);
                e.gatherPropertyReferences(propRefs);
                result = e;
            } else {
                // Let's see if we can find any "naked" $Label.section.name references in the plain text
                Matcher matcher = LABEL_GVP_PATTERN.matcher(raw);
                while (matcher.find()) {
                  String labelRef = matcher.group();
                 
                  if (propRefs == null) {
                      propRefs = Sets.newHashSet();
                  }
                 
          propRefs.add(new PropertyReferenceImpl(labelRef, location));
                }
             
                result = raw;
            }
           
            if (propRefs != null) {
              cmpHandler.addExpressionReferences(propRefs);
            }
           
            return result;
        }

        /**
         * creates a component def ref for this token, either a text or
         * expression component
         *
         * @return - null if undesirable whitespace, else a ComponentDefRef for
         *         the given token
         */
        private ComponentDefRef createComponentDefRef(ExpressionContainerHandler cmpHandler)
                throws AuraValidationException {
            Object value = createValue(cmpHandler);

            boolean IsUndesiredWhitespace = (whitespaceBehavior == WhitespaceBehavior.OPTIMIZE)
                    && (value instanceof String && ((String) value).trim().length() == 0);

            if (value == null || IsUndesiredWhitespace) {
                return null;
            }

            AttributeDefRefImpl.Builder atBuilder = new AttributeDefRefImpl.Builder();
            DefDescriptor<AttributeDef> attdesc = DefDescriptorImpl.getInstance("value", AttributeDef.class);
            atBuilder.setDescriptor(attdesc);
            atBuilder.setLocation(location);
            atBuilder.setValue(value);

            ComponentDefRefImpl.Builder builder = new ComponentDefRefImpl.Builder();
            builder.setDescriptor(type.componentDefDescriptor);
            builder.setAttribute(attdesc, atBuilder.build());
            builder.setLocation(location);
            return builder.build();
        }

        /**
         * @return Returns the type.
         */
        public TokenType getType() {
            return type;
        }

        /**
         * @return the raw string value for this token
         */
        public String getRawValue() {
            return text.substring(begin, end);
        }

    }
}
TOP

Related Classes of org.auraframework.impl.util.TextTokenizer$Token

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.