| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.netbeans.modules.glassfish.tooling.admin; |
| |
| import java.io.IOException; |
| import java.io.Reader; |
| import java.io.Writer; |
| import org.netbeans.modules.glassfish.tooling.logging.Logger; |
| import org.netbeans.modules.glassfish.tooling.utils.CyclicStringBuffer; |
| import org.netbeans.modules.glassfish.tooling.utils.LinkedList; |
| import org.netbeans.modules.glassfish.tooling.utils.OsUtils; |
| |
| /** |
| * Parse process IO and verify it against content verification data. |
| * <p/> |
| * @author Tomas Kraus |
| */ |
| public class ProcessIOParser { |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // Inner classes // |
| //////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Parse process output. |
| * <p/> |
| * Process output is being read as text lines separated by prompt string, |
| * CR or CRLF. Each finished line is searched for provided process |
| * IO content. |
| */ |
| protected static class Parser { |
| |
| /** |
| * State machine input classes. |
| */ |
| protected enum Input { |
| /** Content of user, password hash or tool strings. */ |
| STRING, |
| /** Separator character. */ |
| PROMPT, |
| /** CR character, beginning of CRLF sequence. */ |
| CR, |
| /** LF Character. */ |
| LF; |
| |
| /** Enumeration length. */ |
| protected static final int length = Input.values().length; |
| |
| /** |
| * Get input class value for provided character. |
| * <p/> |
| * @param c Character to check tor input class. |
| * @return Input class of provided character. |
| */ |
| protected static Input value(final char c, final String prompt, |
| final CyclicStringBuffer promptBuff) { |
| if (prompt != null && promptBuff.equals(prompt)) { |
| return PROMPT; |
| } |
| switch (c) { |
| case '\r': |
| return CR; |
| case '\n': |
| return LF; |
| default: |
| return STRING; |
| } |
| } |
| |
| } |
| |
| /** |
| * State machine internal states. |
| */ |
| protected enum State { |
| /** Initial state, expecting line 1st character. */ |
| START, |
| /** Reading line characters until line separator or prompt. */ |
| LINE, |
| /** Got '\r', expecting '\n' from EOL. */ |
| CR, |
| /** Error state. */ |
| ERROR; |
| |
| /** Enumeration length. */ |
| protected static final int length = State.values().length; |
| |
| /** Transition table for [State, Input]. */ |
| protected static final State transition[][] = { |
| // STRING PROMPT CR LF |
| { LINE, START, CR, START}, // START |
| { LINE, START, CR, START}, // LINE |
| { LINE, START, CR, START}, // CR |
| {ERROR, ERROR, ERROR, ERROR} // ERROR |
| }; |
| |
| /** |
| * State machine transition. |
| * <p/> |
| * @param s Current machine state. |
| * @param i current input class. |
| * @return Next machine state. |
| */ |
| protected static State next(final State s, final Input i) { |
| return transition[s.ordinal()][i.ordinal()]; |
| } |
| |
| } |
| |
| /** Content to verify on server administration command execution IO. */ |
| private final ProcessIOContent content; |
| |
| /** Content token to be verified. */ |
| private ProcessIOContent.Token token; |
| |
| /** Content verification result. */ |
| private ProcessIOResult result; |
| |
| /** Process input prompt length. Value of zero indicates no prompt. */ |
| private int promptLen; |
| |
| /** Current line being processed. */ |
| private final StringBuilder line; |
| |
| /** Cyclic buffer to compare input against prompt. */ |
| CyclicStringBuffer promptBuff; |
| |
| /** Machine internal state. */ |
| private State state; |
| |
| /** Process output log lines. */ |
| private final LinkedList<String> output; |
| |
| /** |
| * Creates an instance of process output parser. |
| * <p/> |
| * @param content Content to verify on server administration command |
| * execution IO. |
| */ |
| protected Parser(ProcessIOContent content) { |
| this.content = content; |
| line = new StringBuilder(BUFF_SIZE); |
| state = State.START; |
| token = this.content.firstToken(); |
| result = ProcessIOResult.UNKNOWN; |
| String prompt = content.getCurrentPrompt(); |
| promptLen = prompt != null ? prompt.length() : 0; |
| promptBuff = new CyclicStringBuffer(promptLen); |
| output = new LinkedList<>(); |
| } |
| |
| /** |
| * Parses content of process output. |
| * <p/> |
| * @param buff Buffer with incoming process standard output data. |
| * @param len Data length in process standard output buffer. |
| */ |
| protected void parse(final char[] buff, final short len) { |
| for (int pos = 0; pos < len; pos++) { |
| state = action(buff[pos]); |
| } |
| } |
| |
| /** |
| * Finish parsing when end of file was reached. |
| */ |
| protected void finish() { |
| endOfLine('\0'); |
| } |
| |
| /** |
| * Get content verification result. |
| * <p/> |
| * @return Content verification result. |
| */ |
| protected ProcessIOResult result() { |
| return result; |
| } |
| |
| /** |
| * Run parser action based on current state and character class. |
| * <p/> |
| * @param c Current character being processed from {@link Reader} buffer. |
| * @return Next state transition based on current state |
| * and character class. |
| */ |
| @SuppressWarnings("fallthrough") |
| protected State action(final char c) { |
| Input cl = Input.value(c, content.getCurrentPrompt(), promptBuff); |
| switch (state) { |
| case START: switch (cl) { |
| case STRING: |
| firstChar(c); |
| break; |
| case PROMPT: |
| firstChar(c); |
| case LF: |
| endOfLine(c); |
| } break; |
| case LINE: switch (cl) { |
| case STRING: |
| nextChar(c); |
| break; |
| case PROMPT: |
| nextChar(c); |
| case LF: |
| endOfLine(c); |
| } break; |
| case CR: switch (cl) { |
| case STRING: |
| nextCharWithCR(c); |
| break; |
| case PROMPT: |
| nextCharWithCR(c); |
| case LF: |
| endOfLine(c); |
| } break; |
| } |
| return State.next(state, cl); |
| } |
| |
| /** |
| * Clear line content and append first character. |
| * <p/> |
| * @param c Current character from buffer. |
| */ |
| protected void firstChar(final char c) { |
| line.setLength(0); |
| line.append(c); |
| } |
| |
| /** |
| * Append next character. |
| * <p/> |
| * @param c Current character from buffer. |
| */ |
| protected void nextChar(final char c) { |
| line.append(c); |
| } |
| |
| /** |
| * Append next character after CR. |
| * <p/> |
| * @param c Current character from buffer. |
| */ |
| protected void nextCharWithCR(final char c) { |
| line.append('\r'); |
| line.append(c); |
| } |
| |
| /** |
| * Handle end of line. |
| * <p/> |
| * @param c Current character from buffer (not used). |
| */ |
| protected void endOfLine(final char c) { |
| if (line.length() > 0) { |
| output.addLast(line.toString()); |
| } |
| if (token != null) { |
| ProcessIOResult matchResult |
| = ProcessIOResult.UNKNOWN; |
| for (int i = 0 ; i < line.length() ; i++) { |
| if ((matchResult = token.match(line, i)) |
| != ProcessIOResult.UNKNOWN) { |
| token = content.nextToken(); |
| String prompt = content.getCurrentPrompt(); |
| promptLen = prompt != null ? prompt.length() : 0; |
| promptBuff.resize(promptLen); |
| break; |
| } |
| } |
| switch(matchResult) { |
| case SUCCESS: |
| if (result == ProcessIOResult.UNKNOWN) { |
| result = matchResult; |
| } |
| break; |
| case ERROR: |
| if (result != ProcessIOResult.ERROR) { |
| result = matchResult; |
| } |
| break; |
| } |
| } |
| line.setLength(0); |
| } |
| |
| /** |
| * Build output string from stored process output lines. |
| * <p/> |
| * @return Process output string. |
| */ |
| protected String getOutputString() { |
| int len = 0; |
| boolean isElement = output.first(); |
| while(isElement) { |
| len += output.getCurrent().length(); |
| isElement = output.next(); |
| if (isElement) { |
| len += OsUtils.LINES_SEPARATOR.length(); |
| } |
| } |
| StringBuilder sb = new StringBuilder(len); |
| isElement = output.first(); |
| while(isElement) { |
| sb.append(output.getCurrent()); |
| isElement = output.next(); |
| if (isElement) { |
| sb.append(OsUtils.LINES_SEPARATOR); |
| } |
| } |
| return sb.toString(); |
| } |
| |
| } |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // Class attributes // |
| //////////////////////////////////////////////////////////////////////////// |
| |
| /** Logger instance for this class. */ |
| private static final Logger LOGGER = new Logger(ProcessIOParser.class); |
| |
| /** Internal IO buffer size. */ |
| private static final short BUFF_SIZE = 128; |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // Instance attributes // |
| //////////////////////////////////////////////////////////////////////////// |
| |
| /** Process standard input. */ |
| private final Writer stdIn; |
| |
| /** Process standard output. */ |
| private final Reader stdOut; |
| |
| /** Process standard output parser. */ |
| private final Parser outParser; |
| |
| /** Buffer for incoming process standard output data. */ |
| private final char[] outBuff; |
| |
| /** Data length in process standard output buffer. */ |
| private short outLen; |
| |
| /** Vas process output verification already done? */ |
| private boolean verifydone; |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // Constructors // |
| //////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Creates an instance of process IO verification parser. |
| * <p/> |
| * @param stdIn Process standard input. |
| * @param stdOut Process standard output. |
| * @param ioContent Content to verify on server administration command |
| * execution IO. |
| */ |
| public ProcessIOParser(final Writer stdIn, final Reader stdOut, |
| final ProcessIOContent ioContent) { |
| this.stdIn = stdIn; |
| this.stdOut = stdOut; |
| outParser = new Parser(ioContent); |
| outBuff = new char[BUFF_SIZE]; |
| outLen = 0; |
| verifydone = false; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////// |
| // Methods // |
| //////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Verify process output streams against content verification data |
| * provided in constructor as <code>ioContent</code> argument. |
| * <p/> |
| * @return Process output streams verification result. |
| * @throws IOException When there is an issue with reading process |
| * output streams. |
| */ |
| public ProcessIOResult verify() throws IOException { |
| while (outLen >= 0) { |
| outLen = (short)stdOut.read(outBuff); |
| outParser.parse(outBuff, outLen); |
| } |
| outParser.finish(); |
| verifydone = true; |
| return outParser.result(); |
| } |
| |
| /** |
| * Return process output as {@link String}. |
| * <p/> |
| * @return Process output as {@link String}. |
| */ |
| public String getOutput() { |
| final String METHOD = "getOutput"; |
| if (!verifydone) { |
| throw new CommandException(LOGGER.excMsg(METHOD, "illegalState")); |
| } |
| return outParser.getOutputString(); |
| } |
| } |