Package ca.carleton.gcrc.couch.app.impl

Source Code of ca.carleton.gcrc.couch.app.impl.DocumentFile

package ca.carleton.gcrc.couch.app.impl;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;

import ca.carleton.gcrc.couch.app.Attachment;
import ca.carleton.gcrc.couch.app.Document;
import ca.carleton.gcrc.couch.app.DocumentStoreProcess;
import ca.carleton.gcrc.couch.fsentry.FSEntry;
import ca.carleton.gcrc.couch.fsentry.FSEntryNameFilter;
import ca.carleton.gcrc.couch.fsentry.FSEntryNull;
import ca.carleton.gcrc.couch.fsentry.FSEntrySupport;

public class DocumentFile implements Document {

  static final private Pattern patternFileNameAndExtension = Pattern.compile("^(.*)\\.([^.]*)$");
  static final private Pattern patternInteger = Pattern.compile("^([0-9]+)$");
  static final private Pattern patternInclusion = Pattern.compile("!code\\s+([^ \\t\\r\\n]*)");

  static public FSEntryNameFilter defaultFilenameFilter = new FSEntryNameFilter() {
    @Override
    public boolean accept(FSEntry dir, String name) {
      if( name.startsWith(".") ) {
        return false;
      }
      return true;
    }
  };
  static public FSEntry defaultIncludeEntry = new FSEntryNull();
 
  static public DocumentFile createDocument(FSEntry directory) throws Exception {
    return createDocument(directory, defaultFilenameFilter, defaultIncludeEntry);
  }
 
  static public DocumentFile createDocument(FSEntry directory, FSEntryNameFilter filter) throws Exception {
    return createDocument(directory, filter, defaultIncludeEntry);
  }
 
  static public DocumentFile createDocument(FSEntry directory, FSEntry includeDir) throws Exception {
    return createDocument(directory, defaultFilenameFilter, includeDir);
  }
 
  static public DocumentFile createDocument(FSEntry directory, FSEntryNameFilter filter, FSEntry includeDir) throws Exception {
    // Check that directory exists and is a directory
    if( null == directory ) {
      throw new Exception("Directory required to build a document");
    }
   
    if( null == filter ){
      filter = defaultFilenameFilter;
    }
   
    if( null == includeDir ){
      includeDir = defaultIncludeEntry;
    }
   
    DocumentFile doc = new DocumentFile(directory, filter, includeDir);
   
    return doc;
  }
 
  private FSEntry top;
  private FSEntryNameFilter filter = null;
  private FSEntry includeDir;
  private JSONObject jsonObj = null;
  private List<AttachmentFile> attachments = new Vector<AttachmentFile>();
  private String ATT_INFO_PERIOD_EXTENSION = "."+DocumentStoreProcess.ATT_INFO_EXTENSION;

  private DocumentFile(FSEntry directory, FSEntryNameFilter filter, FSEntry includeDir) throws Exception {
    this.top = directory;
    this.filter = filter;
    this.includeDir = includeDir;
   
    readTopDirectory();
  }
 
  @Override
  public String getId() {
    if( null != jsonObj ) {
      String id = jsonObj.optString("_id");
      return id;
    }
    return null;
  }

  @Override
  public void setId(String id) throws Exception {
    jsonObj.put("_id", id);
  }

  @Override
  public String getRevision() {
    if( null != jsonObj ) {
      String id = jsonObj.optString("_rev");
      return id;
    }
    return null;
  }

  @Override
  public JSONObject getJSONObject() {
    return jsonObj;
  }

  @Override
  public Collection<Attachment> getAttachments() {
    return new ArrayList<Attachment>(attachments);
  }

  private void readTopDirectory() throws Exception {
    this.jsonObj = new JSONObject();
   
    // List all elements in directory
    List<FSEntry> children = this.top.getChildren(filter);
   
    // Process each child
    for(FSEntry child : children) {
      String childName = child.getName();

      // Handle attachments in a special manner
      if( "_attachments".equals(childName) ) {
        createAttachmentsFromDirectory("", child);
      } else {
       
        // Compute name without extension, which is the key
        String key = null;
        boolean isChildAnArray = false;
        boolean isChildJson = false;
        {
          Matcher faeMatcher = patternFileNameAndExtension.matcher(childName);
          if( faeMatcher.matches() ) {
            key = faeMatcher.group(1);
           
            String ext = faeMatcher.group(2);
            isChildAnArray = "array".equals(ext);
            isChildJson = "json".equals(ext);
          } else {
            key = childName;
          }
        }
 
        // Read value
        if( child.isDirectory() ) {
          if( isChildAnArray ) {
            // Child is an array
            JSONArray value = readArrayDirectory(child);
            this.jsonObj.put(key, value);
          } else {
            // Child is an object
            JSONObject value = readObjectDirectory(child);
            this.jsonObj.put(key, value);
          }
        } else if( child.isFile() ) {
          if( isChildJson ) {
            Object value = readJsonFile(child);
            this.jsonObj.put(key, value);
          } else {
            String value = readStringFile(child);
           
            // Remove EOL at end of _id attribute. This is to avoid
            // issues with some editors (vi) that automatically add an EOL
            // at the end of a text file.
            if( "_id".equals(key) ) {
              value = value.trim();
            }
           
            this.jsonObj.put(key, value);
          }
        } else {
          throw new Exception("Does not know how to handle children element of top object: "+childName);
        }
      }
    }
  }
 
  private void createAttachmentsFromDirectory(String parentName, FSEntry dir) throws Exception {
    // List all elements in directory
    List<FSEntry> children = dir.getChildren(filter);
   
    // Process each child
    for(FSEntry child : children) {
      String childName = child.getName();
     
      if( childName.endsWith(ATT_INFO_PERIOD_EXTENSION) ) {
        // Skip info files
       
      } else if( child.isDirectory() ) {
        String name = parentName + childName + "/";
        createAttachmentsFromDirectory(name, child);
       
      } else if( child.isFile() ) {
        String name = parentName + childName;
        createAttachmentFromFile(dir, name, child);
      }
    }
  }

  private void createAttachmentFromFile(FSEntry parentDir, String name, FSEntry file) throws Exception {
   
    String attachmentName = name;
    String contentType = null;
   
    // Look for info file
    {
      FSEntry infoFile = parentDir.getChild(name+ATT_INFO_PERIOD_EXTENSION);
      if( null != infoFile
       && infoFile.isFile() ) {
        // Load and interpret info file
        InputStream fis = null;
        char[] buffer = new char[100];
        try {
          fis = infoFile.getInputStream();
          InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
          StringWriter sw = new StringWriter();

          int size = isr.read(buffer);
          while( size >= 0 ) {
            sw.write(buffer, 0, size);
            size = isr.read(buffer);
          }
         
          sw.flush();
         
          fis.close();
          fis = null;
         
          JSONTokener tokener = new JSONTokener(sw.toString());
          Object infoObj = tokener.nextValue();
          if( infoObj instanceof JSONObject ) {
            JSONObject info = (JSONObject)infoObj;
           
            {
              String testName = info.optString(DocumentStoreProcess.ATT_INFO_NAME);
              if( null == testName ){
                // Ignore
              } else if("".equals(testName)) {
                // Ignore
              } else {
                attachmentName = testName;
              }
            }

            {
              String testContentType = info.optString(DocumentStoreProcess.ATT_INFO_CONTENT_TYPE);
              if( null == testContentType ){
                // Ignore
              } else if("".equals(testContentType)) {
                // Ignore
              } else {
                contentType = testContentType;
              }
            }
           
          } else {
            throw new Exception("Unexpected class for information object: "+infoObj.getClass().getName());
          }
         
        } catch(Exception e) {
          throw new Exception("Error while reading info file", e);
        } finally {
          if( null != fis ){
            try {
              fis.close();
            } catch(Exception e) {
              // Ignore
            }
          }
        }
      }
    }

    // Predict content type from extension
    if( null == contentType ) {
      Matcher fnaeMatcher = patternFileNameAndExtension.matcher(name);
      if( fnaeMatcher.matches() ) {
        String extension = fnaeMatcher.group(2);
        MimeType mimeType = MimeTypeDb.getDefaultDB().fromFileExtension(extension);
        if( null != mimeType ){
          contentType = mimeType.getContentType();
        }
      }
    }

    // Default content type
    if( null == contentType ) {
      contentType = "application/binary";
    }
   
    AttachmentFile att = new AttachmentFile(attachmentName, file, contentType);
    this.attachments.add(att);
  }

  private JSONArray readArrayDirectory(FSEntry dir) throws Exception{
    JSONArray arr = new JSONArray();
   
    // List all elements in directory, in sorted order
    List<FSEntry> children = dir.getChildren(filter);
    Collections.sort(children,new Comparator<FSEntry>(){
      @Override
      public int compare(FSEntry o1, FSEntry o2) {
        return o1.getName().compareTo( o2.getName() );
      }
    });
   
    // Process each child
    for(FSEntry child : children) {
      String childName = child.getName();
     
      // Compute name without extension, which is the key
      String keyStr = null;
      boolean isChildAnArray = false;
      boolean isChildJson = false;
      {
        Matcher faeMatcher = patternFileNameAndExtension.matcher(childName);
        if( faeMatcher.matches() ) {
          keyStr = faeMatcher.group(1);
         
          String ext = faeMatcher.group(2);
          isChildAnArray = "array".equals(ext);
          isChildJson = "json".equals(ext);
        } else {
          keyStr = childName;
        }
      }
     
      // The key should be an integer
      {
        Matcher integerMatcher = patternInteger.matcher(keyStr);
        if( false == integerMatcher.matches() ) {
          throw new Exception("An array represented by a directory must have files with integer names: "+keyStr+" ("+childName+")");
        }
      }
     
      // Read value
      if( child.isDirectory() ) {
        if( isChildAnArray ) {
          // Child is an array
          JSONArray value = readArrayDirectory(child);
          arr.put(value);
        } else {
          // Child is an object
          JSONObject value = readObjectDirectory(child);
          arr.put(value);
        }
      } else if( child.isFile() ) {
        if( isChildJson ) {
          Object value = readJsonFile(child);
          arr.put(value);
        } else {
          String value = readStringFile(child);
          arr.put(value);
        }
      } else {
        throw new Exception("Does not know how to handle children element of array: "+childName);
      }
    }
   
    return arr;
  }

  private JSONObject readObjectDirectory(FSEntry dir) throws Exception {
    JSONObject obj = new JSONObject();
   
    // List all elements in directory
    List<FSEntry> children = dir.getChildren(filter);
   
    // Process each child
    for(FSEntry child : children) {
      String childName = child.getName();
     
      // Compute name without extension, which is the key
      String key = null;
      boolean isChildAnArray = false;
      boolean isChildJson = false;
      {
        Matcher faeMatcher = patternFileNameAndExtension.matcher(childName);
        if( faeMatcher.matches() ) {
          key = faeMatcher.group(1);
         
          String ext = faeMatcher.group(2);
          isChildAnArray = "array".equals(ext);
          isChildJson = "json".equals(ext);
        } else {
          key = childName;
        }
      }

      // Read value
      if( child.isDirectory() ) {
        if( isChildAnArray ) {
          // Child is an array
          JSONArray value = readArrayDirectory(child);
          obj.put(key, value);
        } else {
          // Child is an object
          JSONObject value = readObjectDirectory(child);
          obj.put(key, value);
        }
      } else if( child.isFile() ) {
        if( isChildJson ) {
          Object value = readJsonFile(child);
          obj.put(key, value);
        } else {
          String value = readStringFile(child);
          obj.put(key, value);
        }
      } else {
        throw new Exception("Does not know how to handle children element of object: "+childName);
      }
    }
   
    return obj;
  }

  private String readStringFile(FSEntry file) throws Exception {
    StringWriter sw = new StringWriter();
    InputStream is = null;
    char[] buffer = new char[100];
    try {
      is = file.getInputStream();
      InputStreamReader isr = new InputStreamReader(is, "UTF-8");
     
      int size = isr.read(buffer);
      while( size >= 0 ) {
        sw.write(buffer, 0, size);
        size = isr.read(buffer);
      }
     
      sw.flush();
     
    } catch (Exception e) {
      throw new Exception("Error while reading file: "+file.getName(), e);
     
    } finally {
      if( null != is ) {
        try {
          is.close();
        } catch (Exception e) {
          // Ignore
        }
      }
    }
   
    String expandedValue = expandInclusion(sw.toString());
   
    return expandedValue;
  }

  private Object readJsonFile(FSEntry file) throws Exception {
   
    Object result = null;
   
    StringWriter sw = new StringWriter();
    InputStream is = null;
    char[] buffer = new char[100];
    try {
      is = file.getInputStream();
      InputStreamReader isr = new InputStreamReader(is, "UTF-8");
     
      int size = isr.read(buffer);
      while( size >= 0 ) {
        sw.write(buffer, 0, size);
        size = isr.read(buffer);
      }
     
      sw.flush();
     
      JSONTokener tokener = new JSONTokener(sw.toString());
      result = tokener.nextValue();
     
    } catch (Exception e) {
      throw new Exception("Error while reading file: "+file.getName(), e);
     
    } finally {
      if( null != is ) {
        try {
          is.close();
        } catch (Exception e) {
          // Ignore
        }
      }
    }
   
    return result;
  }
 
  /**
   * This method takes a string and expands the inclusion statements found in it.
   * It returns the fully expanded string. An inclusion is a line that contains
   * !code <filename>
   * @param content Initial content
   * @return Expanded content by inserting the included files
   * @throws Exception
   */
  private String expandInclusion(String content) throws Exception{
    // Quick check
    {
      Matcher matcher = patternInclusion.matcher(content);
      if( false == matcher.find() ){
        return content;
      }
    }
   
    // OK, perform processing
    StringWriter output = new StringWriter();
    StringReader sr = new StringReader(content);
    BufferedReader br = new BufferedReader(sr);
    String line = br.readLine();
    while( null != line ){
      Matcher matcher = patternInclusion.matcher(line);
      if( matcher.find() ){
        output.write(line);
       
        String filePath = matcher.group(1);
        FSEntry includedEntry = FSEntrySupport.findDescendant(includeDir, filePath);
        if( null != includedEntry ) {
          output.write("\n");
          insertFile(output, includedEntry);
        } else {
          output.write(" - not found\n");
        }
       
      } else {
        output.write(line);
        output.write("\n");
      }
      line = br.readLine();
    }
   
    return output.toString();
  }

  private void insertFile(Writer writer, FSEntry includedEntry) throws Exception {
    InputStream is = null;
    char[] buffer = new char[100];
    try {
      is = includedEntry.getInputStream();
      InputStreamReader isr = new InputStreamReader(is, "UTF-8");
     
      int size = isr.read(buffer);
      while( size >= 0 ) {
        writer.write(buffer, 0, size);
        size = isr.read(buffer);
      }
     
      writer.flush();
     
    } catch (Exception e) {
      throw new Exception("Error while reading file: "+includedEntry.getName(), e);
     
    } finally {
      if( null != is ) {
        try {
          is.close();
        } catch (Exception e) {
          // Ignore
        }
      }
    }
  }
}
TOP

Related Classes of ca.carleton.gcrc.couch.app.impl.DocumentFile

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.