/FileExtraction/Java_unseen/dlitz_resin/modules/resin/src/com/caucho/servlets/ssi/SSIParser.java
/*
 * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.servlets.ssi;

import com.caucho.util.ByteBuffer;
import com.caucho.vfs.Path;
import com.caucho.vfs.ReadStream;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

/**
 * Serves server-side include files.
 */
public class SSIParser {
  private int _line;

  private final SSIFactory _factory;

  public SSIParser()
  {
    _factory = new SSIFactory();
  }

  public SSIParser(SSIFactory factory)
  {
    _factory = factory;
  }

  Statement parse(Path path)
    throws IOException
  {
    ReadStream is = path.openRead();

    try {
      ArrayList<Statement> statements = new ArrayList<Statement>();
      
      parse(is, statements);

      return new BlockStatement(statements);
    } finally {
      is.close();
    }
  }

  Statement parse(ReadStream is)
    throws IOException
  {
    ArrayList<Statement> statements = new ArrayList<Statement>();
      
    parse(is, statements);

    return new BlockStatement(statements);
  }

  /**
   * Parses a list of statements from the ssi stream.
   */
  private void parse(ReadStream is, ArrayList<Statement> statements)
    throws IOException
  {
    ByteBuffer bb = new ByteBuffer();
    int ch;

    while ((ch = is.read()) >= 0) {
      if (ch != '<') {
        if (ch == '\n')
          _line++;

        bb.append(ch);
      }
      else if ((ch = is.read()) != '!') {
        bb.append('<');

        is.unread();
      }
      else if ((ch = is.read()) != '-') {
        bb.append('<');
        bb.append('!');

        is.unread();
      }
      else if ((ch = is.read()) != '-') {
        bb.append('<');
        bb.append('!');
        bb.append('-');

        is.unread();
      }
      else if ((ch = is.read()) != '#') {
        bb.append('<');
        bb.append('!');
        bb.append('-');
        bb.append('-');

        is.unread();
      }
      else {
        if (bb.getLength() > 0) {
          TextStatement text;

          text = new TextStatement(bb.getBuffer(), 0, bb.getLength());

          statements.add(text);
          bb.clear();
        }

        Statement stmt = parseCommand(is);

        statements.add(stmt);
        
        if (stmt instanceof IfStatement) {
          parseIf(is, (IfStatement) stmt);
        }
        
        if (stmt instanceof ElifStatement) {
          return;
        }
        else if (stmt instanceof ElseStatement) {
          return;
        }
        else if (stmt instanceof EndifStatement) {
          return;
        }
      }
    }

    if (bb.getLength() > 0) {
      statements.add(new TextStatement(bb.getBuffer(), 0, bb.getLength()));
      bb.clear();
    }
  }

  private void parseIf(ReadStream is, IfStatement ifStmt)
    throws IOException
  {
    ArrayList<Statement> trueBlock = new ArrayList<Statement>();

    parse(is, trueBlock);
    int size = trueBlock.size();

    if (size > 0 && trueBlock.get(size - 1) instanceof ElifStatement) {
      ElifStatement elifBlock = (ElifStatement) trueBlock.get(size - 1);
      trueBlock.remove(size - 1);

      ifStmt.setFalseBlock(elifBlock);
    }
    else if (size > 0 && trueBlock.get(size - 1) instanceof ElseStatement) {
      ArrayList<Statement> falseBlock = new ArrayList<Statement>();
      
      parse(is, falseBlock);

      ifStmt.setFalseBlock(new BlockStatement(falseBlock));
    }
    
    ifStmt.setTrueBlock(new BlockStatement(trueBlock));
  }

  private Statement parseCommand(ReadStream is)
    throws IOException
  {
    StringBuilder sb = new StringBuilder();

    int ch;

    while (Character.isLetterOrDigit((ch = is.read()))) {
      sb.append((char) ch);
    }

    String cmd = sb.toString();

    HashMap<String,String> attr = parseAttributes(is);

    if ((ch = is.read()) != '-') {
    }
    else if ((ch = is.read()) != '-') {
    }
    else if ((ch = is.read()) != '>') {
    }

    Statement statement = _factory.createStatement(cmd, attr, is.getPath());

    if (statement == null)
      statement = new ErrorStatement("['" + cmd + "' is an unknown command.]");

    return statement;
  }

  private HashMap<String,String> parseAttributes(ReadStream is)
    throws IOException
  {
    HashMap<String,String> attr = new HashMap<String,String>();

    while (true) {
      int ch;

      while (Character.isWhitespace((ch = is.read()))) {
      }

      StringBuilder key = new StringBuilder();

      for (; Character.isLetterOrDigit(ch); ch = is.read()) {
        key.append((char) ch);
      }

      for (; Character.isWhitespace(ch); ch = is.read()) {
      }

      if (ch != '=')
        return attr;

      for (ch = is.read(); Character.isWhitespace(ch); ch = is.read()) {
      }

      StringBuilder value = new StringBuilder();

      if (ch == '\'' || ch == '"') {
        int end = ch;

        for (ch = is.read(); ch > 0 && ch != end; ch = is.read()) {
          if (ch == '\\') {
            ch = is.read();

            if (ch == '\'' || ch == '\"')
              value.append((char) ch);
            else {
              value.append('\\');
              is.unread();
            }
          }
          else
            value.append((char) ch);
        }
      }
      else {
        for (; ch > 0 && ! Character.isWhitespace(ch); ch = is.read()) {
          value.append((char) ch);
        }
      }

      attr.put(key.toString(), value.toString());
    }
  }
}
