PropertyPathTokenizer.java

package com.gh.mygreen.xlsmapper.util;

import java.util.LinkedList;

import com.gh.mygreen.xlsmapper.util.PropertyPath.Token;

/**
 * プロパティアクセス用の式をパースする。
 * <p>{@link PropertyValueNavigator}で使用する。
 * @since 1.0
 * @author T.TSUCHIE
 *
 */
public class PropertyPathTokenizer {
    
    /**
     * エスケープ文字(バックスペース\\)
     */
    private static final String STR_ESCAPE_CHAR = "\\";
    
    /**
     * プロパティのアクセス用の書式をトークンに分割する。
     * @param path
     * @return
     * @throws IllegalArgumentException path is null or empty.
     */
    public PropertyPath parse(final String path) {
        
        ArgUtils.notEmpty(path, "path");
        
        final PropertyPath tokenStore = new PropertyPath(path);
        splitToken(tokenStore, path);
        return tokenStore;
    }
    
    /**
     * パスを最小限の項目に分割する。
     * 
     * @param tokenStore 分割した結果
     * @param path パス
     * 
     */
    private void splitToken(final PropertyPath tokenStore, final String path) {
        
        // 解析時の途中の文字を一時的に補完しておく。
        final LinkedList<String> stack = new LinkedList<String>();
        
        final int length = path.length();
        for(int i=0; i < length; i++) {
            final char c = path.charAt(i);
            
            if(StackUtils.equalsTopElement(stack, STR_ESCAPE_CHAR)) {
                // スタックの一番上がエスケープの文字の場合、通常の文字として扱う。
                stack.push(String.valueOf(c));
                
            } else if (c == '.') {
                
                if(StackUtils.equalsBottomElement(stack, "[")) {
                    // キーの囲み文字の途中の場合、文字列としてスタックに積む。
                    stack.push(String.valueOf(c));
                    
                } else if(!stack.isEmpty()) {
                    // 既に文字が入っている場合は、既存のものを取り出し分割する。
                    tokenStore.add(new Token.Name(StackUtils.popupAndConcat(stack).trim()));
                    tokenStore.add(new Token.Separator());
                    
                } else {
                    tokenStore.add(new Token.Separator());
                }
                
            } else if(c == '[') {
                
                if(!stack.isEmpty()) {
                    // 既に文字が入っている場合は、既存のものを取り出し分割する。
                    tokenStore.add(new Token.Name(StackUtils.popupAndConcat(stack).trim()));
                }
                
                stack.push(String.valueOf(c));
                
            } else if(c == ']') {
                if(StackUtils.equalsBottomElement(stack, "[")) {
                    // 条件の終わりの場合
                    tokenStore.add(new Token.Key(StackUtils.popupAndConcat(stack) + c));
                    
                } else {
                    stack.push(String.valueOf(c));
                }
                
            } else if(c == ' ') {
                // キーの囲み文字の中であれば、空白は無視しない。
                if(StackUtils.equalsBottomElement(stack, "[")) {
                    stack.push(String.valueOf(c));
                }
                
            } else {
                stack.push(String.valueOf(c));
            }
            
        }
        
        if(!stack.isEmpty()) {
            tokenStore.add(new Token.Name(StackUtils.popupAndConcat(stack).trim()));
        }
        
    }
    
}