/* * JBoss, Home of Professional Open Source. * Copyright 2015, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.cli.handlers.ifelse; import org.jboss.as.cli.CommandFormatException; import org.jboss.as.cli.CommandLineException; import org.jboss.as.cli.handlers.CommandHandlerWithArguments; import org.jboss.as.cli.impl.ArgumentWithValue; import org.jboss.as.cli.operation.ParsedCommandLine; import org.jboss.as.cli.parsing.CharacterHandler; import org.jboss.as.cli.parsing.DefaultParsingState; import org.jboss.as.cli.parsing.DefaultStateWithEndCharacter; import org.jboss.as.cli.parsing.EscapeCharacterState; import org.jboss.as.cli.parsing.ExpressionBaseState; import org.jboss.as.cli.parsing.GlobalCharacterHandlers; import org.jboss.as.cli.parsing.ParsingContext; import org.jboss.as.cli.parsing.ParsingState; import org.jboss.as.cli.parsing.ParsingStateCallbackHandler; import org.jboss.as.cli.parsing.QuotesState; import org.jboss.as.cli.parsing.StateParser; /** * * @author Alexey Loubyansky */ public class ConditionArgument extends ArgumentWithValue { private static final ParsingState AND = new OperationParsingState(AndOperation.SYMBOL) { @Override BaseOperation createOperation() { return new AndOperation(); } }; private static final ParsingState OR = new OperationParsingState(OrOperation.SYMBOL) { @Override BaseOperation createOperation() { return new OrOperation(); } }; private static final ParsingState GT = new OperationParsingState(GreaterThanOperation.SYMBOL) { @Override BaseOperation createOperation() { return new GreaterThanOperation(); } }; private static final ParsingState LT = new OperationParsingState(LesserThanOperation.SYMBOL) { @Override BaseOperation createOperation() { return new LesserThanOperation(); } }; private static final ParsingState NEQ = new OperationParsingState(NotEqualsOperation.SYMBOL) { @Override BaseOperation createOperation() { return new NotEqualsOperation(); } }; private static final ParsingState EQ = new OperationParsingState(EqualsOperation.SYMBOL) { @Override BaseOperation createOperation() { return new EqualsOperation(); } }; private static final ParsingState NGT = new OperationParsingState(NotGreaterThanOperation.SYMBOL) { @Override BaseOperation createOperation() { return new NotGreaterThanOperation(); } }; private static final ParsingState NLT = new OperationParsingState(NotLesserThanOperation.SYMBOL) { @Override BaseOperation createOperation() { return new NotLesserThanOperation(); } }; private static final ParsingState MCH = new OperationParsingState(MatchOperation.SYMBOL) { @Override BaseOperation createOperation() { return new MatchOperation(); } }; private DefaultParsingState parenthesisState; private ExpressionBaseState exprState; public ConditionArgument(CommandHandlerWithArguments handler) { super(handler, 0, "--condition"); } @Override protected ParsingState initParsingState() { final CharacterHandler defaultHandler = new CharacterHandler(){ @Override public void handle(ParsingContext ctx) throws CommandFormatException { final char c = ctx.getCharacter(); if(c == '&' && isFollowingChar(ctx, '&')) { signalState(ctx, AND, true); } else if(c == '|' && isFollowingChar(ctx, '|')) { signalState(ctx, OR, true); } else if(c == '=' && isFollowingChar(ctx, '=')) { signalState(ctx, EQ, true); } else if(c == '~' && isFollowingChar(ctx, '=')) { signalState(ctx, MCH, true); } else if(c == '!' && isFollowingChar(ctx, '=')) { signalState(ctx, NEQ, true); } else if(c == '>' && isFollowingChar(ctx, '=')) { signalState(ctx, NLT, true); } else if(c == '<' && isFollowingChar(ctx, '=')) { signalState(ctx, NGT, true); } else if(c == '>' && !isFollowingChar(ctx, '=')) { signalState(ctx, GT, false); } else if(c == '<' && !isFollowingChar(ctx, '=')) { signalState(ctx, LT, false); } else { ctx.getCallbackHandler().character(ctx); } } protected void signalState(final ParsingContext ctx, final ParsingState state, boolean advance) throws CommandFormatException { ctx.enterState(state); if(advance) { ctx.advanceLocation(1); } ctx.leaveState(); } protected boolean isFollowingChar(ParsingContext ctx, char c) { if (ctx.getLocation() + 1 < ctx.getInput().length()) { return ctx.getInput().charAt(ctx.getLocation() + 1) == c; } return false; }}; exprState = new ExpressionBaseState("EXPR", true, false); exprState.setEnterHandler(GlobalCharacterHandlers.CONTENT_CHARACTER_HANDLER); exprState.setDefaultHandler(defaultHandler); exprState.enterState('"', QuotesState.QUOTES_INCLUDED); exprState.enterState('\\', EscapeCharacterState.INSTANCE); exprState.putHandler(')', new CharacterHandler() { @Override public void handle(ParsingContext ctx) throws CommandFormatException { ctx.leaveState(); if(ctx.getState() == parenthesisState) { ctx.leaveState(); } }}); parenthesisState = new DefaultStateWithEndCharacter("PARENTHESIS", ')', true, true); parenthesisState.enterState('\\', EscapeCharacterState.INSTANCE); parenthesisState.enterState('"', QuotesState.QUOTES_INCLUDED); parenthesisState.enterState('(', parenthesisState); exprState.enterState('(', parenthesisState); parenthesisState.setDefaultHandler(new CharacterHandler() { @Override public void handle(ParsingContext ctx) throws CommandFormatException { ctx.enterState(exprState); }}); //parenthesisState.setReturnHandler(GlobalCharacterHandlers.LEAVE_STATE_HANDLER); DefaultParsingState initial = new DefaultParsingState("EXPR_INITIAL"); initial.enterState('(', parenthesisState); initial.setDefaultHandler(new CharacterHandler(){ @Override public void handle(ParsingContext ctx) throws CommandFormatException { ctx.enterState(exprState); }}); return initial; } @Override public String getResolvedValue(ParsedCommandLine parsedLine, boolean required) throws CommandFormatException { final String value = getOriginalValue(parsedLine, required); if(value == null) { return null; } final StringBuilder buf = new StringBuilder(); StateParser.parse(value, new ParsingStateCallbackHandler(){ @Override public void enteredState(ParsingContext ctx) throws CommandFormatException { } @Override public void leavingState(ParsingContext ctx) throws CommandFormatException { } @Override public void character(ParsingContext ctx) throws CommandFormatException { buf.append(ctx.getCharacter()); }}, initialState); return buf.toString(); } public Operation resolveOperation(ParsedCommandLine parsedLine) throws CommandFormatException { final String value = getOriginalValue(parsedLine, true); if(value == null) { return null; } //System.out.println("PARSING '" + value + "'"); final ConditionOperationCallback callback = new ConditionOperationCallback(); StateParser.parse(value, callback, initialState); return callback.expr.getExpression(); } private final class ConditionOperationCallback implements ParsingStateCallbackHandler { final StringBuilder buf = new StringBuilder(); ExpressionParsingState expr = new ExpressionParsingState(); @Override public void enteredState(ParsingContext ctx) throws CommandFormatException { //System.out.println("entered " + ctx.getState().getId()); if(ctx.getState() == parenthesisState) { expr = new ExpressionParsingState(expr); } } @Override public void leavingState(ParsingContext ctx) throws CommandFormatException { final ParsingState state = ctx.getState(); //System.out.println("left " + state.getId()); try { if (state instanceof OperationParsingState) { BaseOperation nextOperation = ((OperationParsingState) state).createOperation(); final Operand nextOperand; if(expr.nestedExpression != null) { nextOperand = expr.nestedExpression; expr.nestedExpression = null; } else if(nextOperation instanceof ComparisonOperation) { nextOperand = new ModelNodePathOperand(buf.toString()); } else { nextOperand = new StringValueOperand(buf.toString()); } expr.place(nextOperand, nextOperation); buf.setLength(0); } else if (state == exprState) { if(buf.length() > 0) { if(expr.lastParsed == null) { throw new CommandFormatException("The operation is missing in front of '" + buf.toString() + "'"); } final Operand operand = new StringValueOperand(buf.toString()); expr.lastParsed.addOperand(operand); buf.setLength(0); } else if(expr.nestedExpression != null) { if(expr.lastParsed == null) { throw new CommandFormatException("The operation is missing in front of '" + expr.nestedExpression + "'"); } expr.lastParsed.addOperand(expr.nestedExpression); expr.nestedExpression = null; } } else if(state == parenthesisState) { if(expr.parent != null) { expr.parent.nestedExpression = expr.root; expr = expr.parent; } } } catch (CommandLineException e) { throw new CommandFormatException("Failed to parse if condition", e); } } @Override public void character(ParsingContext ctx) throws CommandFormatException { //System.out.println(ctx.getState().getId() + ": " + ctx.getCharacter()); if(parenthesisState != ctx.getState() && !Character.isWhitespace(ctx.getCharacter())) { buf.append(ctx.getCharacter()); } } } private static class ExpressionParsingState { final ExpressionParsingState parent; BaseOperation root; BaseOperation lastParsed; BaseOperation nestedExpression; ExpressionParsingState() { this(null); } ExpressionParsingState(ExpressionParsingState expr) { this.parent = expr; } BaseOperation getExpression() { return root == null ? nestedExpression : root; } protected void place(final Operand nextOperand, final BaseOperation nextOperation) throws CommandLineException { BaseOperation parent = lastParsed; nextOperation.setParent(parent); lastParsed = nextOperation; //System.out.println("orderOperations " + root + " " + lastParsed + " " + nextOperand); if(parent == null) { root = lastParsed; } //System.out.println(" parent " + parent); if (parent == null) { lastParsed.addOperand(nextOperand); } else if (parent.getPriority() >= lastParsed.getPriority()) { if (parent.allowsMoreArguments()) { parent.addOperand(nextOperand); BaseOperation targetParent = parent; while (targetParent != null) { if (targetParent.getPriority() <= lastParsed.getPriority()) { parent = targetParent; break; } targetParent = targetParent.getParent(); } if (targetParent == null) { lastParsed.addOperand(root); root = lastParsed; } else if (parent.getName().equals(lastParsed.getName())) { lastParsed = parent; } else { lastParsed.addOperand(parent.getLastOperand()); parent.replaceLastOperand(lastParsed); } } else { lastParsed.addOperand(nextOperand); BaseOperation targetParent = parent.getParent(); while (targetParent != null) { if (targetParent.allowsMoreArguments()) { parent = targetParent; break; } targetParent = targetParent.getParent(); } if (!parent.allowsMoreArguments()) { throw new IllegalStateException(); } parent.addOperand(lastParsed); } } else { lastParsed.addOperand(nextOperand); BaseOperation targetParent = parent; while (targetParent != null) { if (targetParent.allowsMoreArguments()) { parent = targetParent; break; } targetParent = targetParent.getParent(); } if (!parent.allowsMoreArguments()) { throw new IllegalStateException(); } parent.addOperand(lastParsed); } //System.out.println("ordered " + root); } } private abstract static class OperationParsingState extends DefaultParsingState { public OperationParsingState(String id) { super(id); } abstract BaseOperation createOperation(); } }