001/****************************************************************
002 * Licensed to the Apache Software Foundation (ASF) under one   *
003 * or more contributor license agreements.  See the NOTICE file *
004 * distributed with this work for additional information        *
005 * regarding copyright ownership.  The ASF licenses this file   *
006 * to you under the Apache License, Version 2.0 (the            *
007 * "License"); you may not use this file except in compliance   *
008 * with the License.  You may obtain a copy of the License at   *
009 *                                                              *
010 *   http://www.apache.org/licenses/LICENSE-2.0                 *
011 *                                                              *
012 * Unless required by applicable law or agreed to in writing,   *
013 * software distributed under the License is distributed on an  *
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
015 * KIND, either express or implied.  See the License for the    *
016 * specific language governing permissions and limitations      *
017 * under the License.                                           *
018 ****************************************************************/
019
020package org.apache.james.mpt.protocol;
021
022import java.io.BufferedReader;
023import java.io.InputStream;
024import java.io.InputStreamReader;
025import java.io.Reader;
026import java.util.ArrayList;
027import java.util.List;
028import java.util.Properties;
029
030import org.apache.james.mpt.api.ProtocolInteractor;
031
032
033/**
034 * A builder which generates scripts from textual input.
035 * 
036 * @author Darrell DeBoer <darrell@apache.org>
037 * 
038 * @version $Revision$
039 */
040public class ProtocolSessionBuilder {
041
042    public static final String LOG = "LOG";
043
044    public static final String WAIT = "WAIT";
045
046    public static final String SERVER_CONTINUATION_TAG = "S: \\+";
047
048    public static final String CLIENT_TAG = "C:";
049
050    public static final String SERVER_TAG = "S:";
051
052    public static final String OPEN_UNORDERED_BLOCK_TAG = "SUB {";
053
054    public static final String CLOSE_UNORDERED_BLOCK_TAG = "}";
055
056    public static final String COMMENT_TAG = "#";
057
058    public static final String SESSION_TAG = "SESSION:";
059
060    public static final String REINIT = "REINIT";
061
062    public static final String TIMER = "TIMER";
063
064    private final Properties variables;
065    
066    public ProtocolSessionBuilder() {
067        variables = new Properties();
068    }
069    
070    /**
071     * Sets a substitution varaible.
072     * The value of a variable will be substituted whereever
073     * ${<code>NAME</code>} is found in the input
074     * where <code>NAME</code> is the name of the variable.
075     * @param name not null
076     * @param value not null
077     */
078    public void setVariable(String name, String value) {
079        variables.put(name, value);
080    }
081    
082    /**
083     * Builds a ProtocolSession by reading lines from the test file with the
084     * supplied name.
085     * 
086     * @param fileName
087     *            The name of the protocol session file.
088     * @return The ProtocolSession
089     */
090    public ProtocolInteractor buildProtocolSession(String fileName)
091            throws Exception {
092        ProtocolInteractor session = new ProtocolSession();
093        addTestFile(fileName, session);
094        return session;
095    }
096    
097    /**
098     * Builds a ProtocolSession by reading lines from the reader.
099     * 
100     * @param scriptName not null
101     * @param reader not null
102     * @return The ProtocolSession
103     */
104    public ProtocolInteractor buildProtocolSession(String scriptName, Reader reader)
105            throws Exception {
106        ProtocolInteractor session = new ProtocolSession();
107        addProtocolLines(scriptName, reader, session);
108        return session;
109    }
110
111
112    /**
113     * Adds all protocol elements from a test file to the ProtocolSession
114     * supplied.
115     * 
116     * @param fileName
117     *            The name of the protocol session file.
118     * @param session
119     *            The ProtocolSession to add the elements to.
120     */
121    public void addTestFile(String fileName, ProtocolInteractor session)
122            throws Exception {
123        // Need to find local resource.
124        InputStream is = this.getClass().getResourceAsStream(fileName);
125        if (is == null) {
126            throw new Exception("Test Resource '" + fileName + "' not found.");
127        }
128
129        addProtocolLines(fileName, is, session);
130    }
131
132    /**
133     * Reads ProtocolElements from the supplied InputStream and adds them to the
134     * ProtocolSession.
135     * @param scriptName
136     *            The name of the source file, for error messages.
137     * @param is
138     *            The input stream containing the protocol definition.
139     * @param session
140     *            The ProtocolSession to add elements to.
141     */
142    public void addProtocolLines(String scriptName, InputStream is, ProtocolInteractor session) throws Exception {
143        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
144        
145        doAddProtocolLines(session, scriptName, reader);
146    }
147
148    /**
149     * Reads ProtocolElements from the supplied Reader and adds them to the
150     * ProtocolSession.
151     * @param scriptName
152     *            The name of the source file, for error messages.
153     * @param reader
154     *            the reader containing the protocol definition.
155     * @param session
156     *            The ProtocolSession to add elements to.
157     */
158    public void addProtocolLines(String scriptName, Reader reader, ProtocolInteractor session) throws Exception {
159        final BufferedReader bufferedReader;
160        if (reader instanceof BufferedReader) {
161            bufferedReader = (BufferedReader) reader;
162        } else {
163            bufferedReader = new BufferedReader(reader);
164        }
165        doAddProtocolLines(session, scriptName, bufferedReader);
166    }
167    
168    /**
169     * Reads ProtocolElements from the supplied Reader and adds them to the
170     * ProtocolSession.
171     * 
172     * @param reader
173     *            the reader containing the protocol definition.
174     * @param session
175     *            The ProtocolSession to add elements to.
176     * @param scriptName
177     *            The name of the source file, for error messages.
178     */
179    private void doAddProtocolLines(ProtocolInteractor session, String scriptName, BufferedReader reader) throws Exception {
180        String line;
181        int sessionNumber = -1;
182        int lineNumber = -1;
183        String lastClientMsg = "";
184        while ((line = reader.readLine()) != null) {
185            line = substituteVariables(line);
186            String location = scriptName + ":" + lineNumber;
187            if (SERVER_CONTINUATION_TAG.equals(line)) {
188                session.CONT(sessionNumber);
189            } else if (line.startsWith(CLIENT_TAG)) {
190                String clientMsg = "";
191                if (line.length() > 3) {
192                    clientMsg = line.substring(3);
193                }
194                session.CL(sessionNumber, clientMsg);
195                lastClientMsg = clientMsg;
196            } else if (line.startsWith(SERVER_TAG)) {
197                String serverMsg = "";
198                if (line.length() > 3) {
199                    serverMsg = line.substring(3);
200                }
201                session.SL(sessionNumber, serverMsg, location, lastClientMsg);
202            } else if (line.startsWith(OPEN_UNORDERED_BLOCK_TAG)) {
203                List<String> unorderedLines = new ArrayList<String>(5);
204                line = reader.readLine();
205
206                while (!line.startsWith(CLOSE_UNORDERED_BLOCK_TAG)) {
207                    if (!line.startsWith(SERVER_TAG)) {
208                        throw new Exception(
209                                "Only 'S: ' lines are permitted inside a 'SUB {' block.");
210                    }
211                    String serverMsg = line.substring(3);
212                    unorderedLines.add(serverMsg);
213                    line = reader.readLine();
214                    lineNumber++;
215                }
216
217                session.SUB(sessionNumber, unorderedLines, location,
218                        lastClientMsg);
219            } else if (line.startsWith(COMMENT_TAG)
220                    || line.trim().length() == 0) {
221                // ignore these lines.
222            } else if (line.startsWith(SESSION_TAG)) {
223                String number = line.substring(SESSION_TAG.length()).trim();
224                if (number.length() == 0) {
225                    throw new Exception("No session number specified");
226                }
227                sessionNumber = Integer.parseInt(number);
228            } else {
229                String prefix = line;
230                if (line.length() > 3) {
231                    prefix = line.substring(0, 3);
232                }
233                throw new Exception("Invalid line prefix: " + prefix);
234            }
235            lineNumber++;
236        }
237    }
238
239    /**
240     * Replaces ${<code>NAME</code>} with variable value.
241     * @param line not null
242     * @return not null
243     */
244    private String substituteVariables(String line) {
245        if (variables.size() > 0) {
246            final StringBuffer buffer = new StringBuffer(line);
247            int start = 0;
248            int end = 0;
249            while (start >= 0 && end >= 0) { 
250                start = buffer.indexOf("${", end);
251                if (start < 0) {
252                    break;
253                }
254                end = buffer.indexOf("}", start);
255                if (end < 0) {
256                    break;
257                }
258                final String name = buffer.substring(start+2, end);
259                final String value = variables.getProperty(name);
260                if (value != null) {
261                    buffer.replace(start, end + 1, value);
262                    final int variableLength = (end - start + 2);
263                    end = end + (value.length() - variableLength);
264                }
265            }
266            line = buffer.toString();
267        }
268        return line;
269    }
270
271}