예제 #1
0
  public ChoiceTracker(Config config, JPF jpf) {
    this.config = config;
    vm = jpf.getVM();
    search = jpf.getSearch();

    String fname = config.getString("choice.trace");
    if (fname == null) {
      isReportExtension = true;
      jpf.addPublisherExtension(ConsolePublisher.class, this);
      // pw is going to be set later
    } else {
      try {
        pw = new PrintWriter(fname);
      } catch (FileNotFoundException fnfx) {
        System.err.println("cannot write choice trace to file: " + fname);
        pw = new PrintWriter(System.out);
      }
    }

    excludes = config.getStringArray("choice.exclude");
    cgClasses = config.getClasses("choice.class");

    format = config.getEnum("choice.format", Format.values(), Format.CG);
    showLocation = config.getBoolean("choice.show_location", true);
  }
예제 #2
0
  @Override
  protected void parseStartElement(
      String uri, String localName, String qName, XMLAttributes attributes) throws Exception {
    try {
      // <Sapelli-Collector-Project>, or <ExCiteS-Collector-Project> (for backwards compatibility)
      if (qName.equals(TAG_PROJECT) || qName.equals(TAG_PROJECT_V1X)) {
        if (project != null) {
          addWarning("Ignoring additional " + TAG_PROJECT + " or " + TAG_PROJECT_V1X + " element.");
          return;
        }

        // Detect format version...
        int formatVersion =
            attributes.getInteger(
                ATTRIBUTE_PROJECT_FORMAT,
                qName.equals(TAG_PROJECT_V1X)
                    ? Format.v1_x.ordinal()
                    : DEFAULT_FORMAT
                        .ordinal()); // default: v1.x for ExCiteS tag, DEFAULT_FORMAT for Sapelli
        // tag.
        // 	too low:
        if (formatVersion < LOWEST_SUPPORTED_FORMAT.ordinal())
          throw new SAXException("Unsupported format version: " + formatVersion);
        // 	too high:
        else if (formatVersion
            > HIGHEST_SUPPORTED_FORMAT
                .ordinal()) { // issue warning and then try to parse as highest supported format
          // (might fail)
          addWarning(
              "Format version reported in XML file ("
                  + formatVersion
                  + ") is unsupported ("
                  + LOWEST_SUPPORTED_FORMAT
                  + " <= supported <= "
                  + HIGHEST_SUPPORTED_FORMAT
                  + "), attempting parsing with rules for version "
                  + HIGHEST_SUPPORTED_FORMAT);
          format = HIGHEST_SUPPORTED_FORMAT;
        }
        //	within range (or default because missing attribute):
        else format = Format.values()[formatVersion];

        // Project...
        project =
            new Project(
                (format == Format.v1_x)
                    ? Project.PROJECT_ID_V1X_TEMP
                    : // for format = 1 we set a temp id value (will be replaced by Form:schema-id)
                    attributes.getRequiredInteger(
                        qName,
                        ATTRIBUTE_PROJECT_ID,
                        "because format is >= 2"), // id is required for format >= 2
                attributes.getRequiredString(TAG_PROJECT, ATTRIBUTE_PROJECT_NAME, true, false),
                attributes.getString(ATTRIBUTE_PROJECT_VARIANT, null, true, false),
                attributes.getString(
                    ATTRIBUTE_PROJECT_VERSION, Project.DEFAULT_VERSION, true, false),
                fingerPrint);

        // Set default language (or "en" if not specified):
        String lang = attributes.getString(ATTRIBUTE_PROJECT_DEFAULT_LANG, null, true, false);
        project.setDefaultLanguage(lang != null ? lang : Project.DEFAULT_DEFAULT_LANGUAGE);
        if (lang == null && format == Format.v2_x)
          addWarning(
              "No valid default language has been specified for this project. Languages should be declared using the BCP-47 syntax (e.g. \"fr-CA\" for Canadian French). English (en) will be set as the default language for this project.");

        // Read startForm ID:
        startFormID = attributes.getString(ATTRIBUTE_PROJECT_START_FORM, null, true, false);

        // Add subtree parsers:
        addSubtreeParser(new ConfigurationParser(this));
        addSubtreeParser(new FormParser(this));
      }
      // <?>
      else addWarning("Ignored unrecognised or invalidly placed/repeated element <" + qName + ">.");
    } catch (Exception e) {
      e.printStackTrace(System.err);
      throw new Exception("Error while parsing element <" + qName + ">: " + e.getMessage(), e);
    }
  }
예제 #3
0
/**
 * Handler for project (i.e. survey) description XML files
 *
 * <p>Currently supported formats are the v1.x format and the new v2.x format. Introducing new
 * format version number is only really necessary when major changes are made that affect (backwards
 * & forwards) compatibility. Rules: - If a file is parsed which does not mention a format: - v1.x
 * is assumed for "pre-Sapelli" projects (which have <ExCiteS-Collector-Project> tag); - v2.x
 * (DEFAULT_FORMAT) is assumed for Sapelli projects (with <Sapelli-Collector-Project> tag). - If a
 * file is parsed which mentions a higher format than the highest supported one the parser will
 * issue a warning and attempt parsing it as the highest supported one (which could fail). - If a
 * file is parsed which mentions a lower format than the lowest supported one the parser will throw
 * an exception.
 *
 * @author mstevens, julia, Michalis Vitos
 */
public class ProjectParser extends DocumentParser {

  // STATICS--------------------------------------------------------

  public static enum Format {
    v0_x, // v0.1: trial versions: never used in field & not supported in any present implementation
    // (only really listed here to reserver value 0 such that v1_x corresponds to 1, etc.)
    v1_x, // v1.x: "pre-Sapelli" versions of the ExCiteS Data Collection platform (used for AP,
    // OIFLEG & Ethiopia/Jed)
    v2_x // v2.x: first series of Sapelli-branded versions
    // future releases...
  }

  public static final Format LOWEST_SUPPORTED_FORMAT = Format.v1_x;
  public static final Format HIGHEST_SUPPORTED_FORMAT =
      Format.values()[Format.values().length - 1]; // last value in enum
  public static final Format DEFAULT_FORMAT = Format.v2_x;

  // Tags:
  private static final String TAG_PROJECT = "SapelliCollectorProject";
  private static final String TAG_PROJECT_V1X = "ExCiteS-Collector-Project";

  // Attributes:
  private static final String ATTRIBUTE_PROJECT_FORMAT = "format";
  private static final String ATTRIBUTE_PROJECT_ID = "id";
  private static final String ATTRIBUTE_PROJECT_NAME = "name";
  private static final String ATTRIBUTE_PROJECT_VARIANT = "variant";
  private static final String ATTRIBUTE_PROJECT_VERSION = "version";
  private static final String ATTRIBUTE_PROJECT_START_FORM = "startForm";
  private static final String ATTRIBUTE_PROJECT_DEFAULT_LANG = "defaultLanguage";

  // Potentially platform-specific parameters:
  public static final String DEFAULT_GENERATED_AUDIO_EXTENSION = "wav";

  // DYNAMICS-------------------------------------------------------
  private Format format = DEFAULT_FORMAT;
  private final String generatedAudioExtension;
  private Integer fingerPrint;
  private Project project;
  private String startFormID;
  private FormSchemaInfoProvider fsiProvider;
  private HashMap<Relationship, String> relationshipToFormID;
  private HashMap<Relationship, List<ConstraintDescription>> relationshipToConstraints;
  private List<PostProcessTask> postProcessingTasks;

  public ProjectParser() {
    this(DEFAULT_GENERATED_AUDIO_EXTENSION);
  }

  public ProjectParser(String generatedAudioExtension) {
    super();
    this.generatedAudioExtension = generatedAudioExtension;
  }

  /** @return the generatedAudioExtension */
  public String getGeneratedAudioExtension() {
    return generatedAudioExtension;
  }

  /**
   * Parses the given XML file to produce a {@link Project} instance.
   *
   * @param xmlFile
   * @return the parsed Project instance
   * @throws Exception
   */
  public Project parseProject(File xmlFile) throws Exception {
    return parseProject(xmlFile, null);
  }

  /**
   * Parses the given XML file to produce a {@link Project} instance. If one is given the {@link
   * FormSchemaInfoProvider} is used to speed up {@link Schema} generation.
   *
   * @param xmlFile
   * @param fsiProvider a {@link FormSchemaInfoProvider}, or {@code null}
   * @return the parsed Project instance
   * @throws Exception
   */
  public Project parseProject(File xmlFile, FormSchemaInfoProvider fsiProvider) throws Exception {
    return parseProject(open(xmlFile), fsiProvider);
  }

  /**
   * Parses the given {@link InputStream}, expected to provide XML file contents, to produce a
   * {@link Project} instance.
   *
   * @param input
   * @return the parsed Project instance
   * @throws Exception
   */
  public Project parseProject(InputStream input) throws Exception {
    return parseProject(input, null);
  }

  /**
   * Parses the given {@link InputStream}, expected to provide XML file contents, to produce a
   * {@link Project} instance. If one is given the {@link FormSchemaInfoProvider} is used to speed
   * up {@link Schema} generation.
   *
   * @param input
   * @param fsiProvider a {@link FormSchemaInfoProvider}, or {@code null}
   * @return the parsed Project instance
   * @throws Exception
   */
  public Project parseProject(InputStream input, FormSchemaInfoProvider fsiProvider)
      throws Exception {
    // (Re)Initialise:
    format = DEFAULT_FORMAT;
    project = null;
    fingerPrint = null;
    startFormID = null;
    this.fsiProvider = fsiProvider;
    if (relationshipToFormID != null) relationshipToFormID.clear();
    if (relationshipToConstraints != null) relationshipToConstraints.clear();
    if (postProcessingTasks != null) postProcessingTasks.clear();

    // Get XML hash:
    UnclosableBufferedInputStream ubInput =
        new UnclosableBufferedInputStream(
            input); // decorate stream to avoid it from being closed and to ensure we can use
    // mark/reset
    ubInput.mark(Integer.MAX_VALUE);
    fingerPrint = (new XMLHasher()).getJavaHashCode(ubInput);
    ubInput.reset();
    ubInput.makeClosable();

    // Parse XML:
    parse(ubInput); // !!!
    return project;
  }

  @Override
  public void startDocument() throws SAXException {
    // does nothing (for now)
  }

  @Override
  public void endDocument() throws SAXException {
    // does nothing (for now)
  }

  @Override
  protected void parseStartElement(
      String uri, String localName, String qName, XMLAttributes attributes) throws Exception {
    try {
      // <Sapelli-Collector-Project>, or <ExCiteS-Collector-Project> (for backwards compatibility)
      if (qName.equals(TAG_PROJECT) || qName.equals(TAG_PROJECT_V1X)) {
        if (project != null) {
          addWarning("Ignoring additional " + TAG_PROJECT + " or " + TAG_PROJECT_V1X + " element.");
          return;
        }

        // Detect format version...
        int formatVersion =
            attributes.getInteger(
                ATTRIBUTE_PROJECT_FORMAT,
                qName.equals(TAG_PROJECT_V1X)
                    ? Format.v1_x.ordinal()
                    : DEFAULT_FORMAT
                        .ordinal()); // default: v1.x for ExCiteS tag, DEFAULT_FORMAT for Sapelli
        // tag.
        // 	too low:
        if (formatVersion < LOWEST_SUPPORTED_FORMAT.ordinal())
          throw new SAXException("Unsupported format version: " + formatVersion);
        // 	too high:
        else if (formatVersion
            > HIGHEST_SUPPORTED_FORMAT
                .ordinal()) { // issue warning and then try to parse as highest supported format
          // (might fail)
          addWarning(
              "Format version reported in XML file ("
                  + formatVersion
                  + ") is unsupported ("
                  + LOWEST_SUPPORTED_FORMAT
                  + " <= supported <= "
                  + HIGHEST_SUPPORTED_FORMAT
                  + "), attempting parsing with rules for version "
                  + HIGHEST_SUPPORTED_FORMAT);
          format = HIGHEST_SUPPORTED_FORMAT;
        }
        //	within range (or default because missing attribute):
        else format = Format.values()[formatVersion];

        // Project...
        project =
            new Project(
                (format == Format.v1_x)
                    ? Project.PROJECT_ID_V1X_TEMP
                    : // for format = 1 we set a temp id value (will be replaced by Form:schema-id)
                    attributes.getRequiredInteger(
                        qName,
                        ATTRIBUTE_PROJECT_ID,
                        "because format is >= 2"), // id is required for format >= 2
                attributes.getRequiredString(TAG_PROJECT, ATTRIBUTE_PROJECT_NAME, true, false),
                attributes.getString(ATTRIBUTE_PROJECT_VARIANT, null, true, false),
                attributes.getString(
                    ATTRIBUTE_PROJECT_VERSION, Project.DEFAULT_VERSION, true, false),
                fingerPrint);

        // Set default language (or "en" if not specified):
        String lang = attributes.getString(ATTRIBUTE_PROJECT_DEFAULT_LANG, null, true, false);
        project.setDefaultLanguage(lang != null ? lang : Project.DEFAULT_DEFAULT_LANGUAGE);
        if (lang == null && format == Format.v2_x)
          addWarning(
              "No valid default language has been specified for this project. Languages should be declared using the BCP-47 syntax (e.g. \"fr-CA\" for Canadian French). English (en) will be set as the default language for this project.");

        // Read startForm ID:
        startFormID = attributes.getString(ATTRIBUTE_PROJECT_START_FORM, null, true, false);

        // Add subtree parsers:
        addSubtreeParser(new ConfigurationParser(this));
        addSubtreeParser(new FormParser(this));
      }
      // <?>
      else addWarning("Ignored unrecognised or invalidly placed/repeated element <" + qName + ">.");
    } catch (Exception e) {
      e.printStackTrace(System.err);
      throw new Exception("Error while parsing element <" + qName + ">: " + e.getMessage(), e);
    }
  }

  @Override
  protected void parseEndElement(String uri, String localName, String qName) throws Exception {
    // </Sapelli-Collector-Project>, or </ExCiteS-Collector-Project> (for backwards compatibility)
    if (qName.equals(TAG_PROJECT) || qName.equals(TAG_PROJECT_V1X)) {
      clearSubtreeParsers();

      if (project.getForms().size() == 0)
        throw new SAXException("A project such have at least 1 form!");
      else {
        // Resolve startForm
        Form startForm =
            project.getForm(
                startFormID); // will return null if startFormID is null or there is no form with
        // that name, uses equalsIgnoreCase()
        if (startForm != null) project.setStartForm(startForm);
        // else: first form of project will remain the startForm

        // Resolve form relationships:
        if (relationshipToFormID != null)
          for (Entry<Relationship, String> entry : relationshipToFormID.entrySet()) {
            Relationship rel = entry.getKey();
            Form relatedForm = project.getForm(entry.getValue()); // uses equalsIgnoreCase()
            if (relatedForm == null)
              throw new SAXException(
                  "Relationship \""
                      + rel.id
                      + "\" in form \""
                      + rel.form.id
                      + "\" refers to unknown related form \""
                      + entry.getValue()
                      + "\".");
            rel.setRelatedForm(
                relatedForm); // will trigger initialisation of Schema of relatedForm (this should
            // not be a problem, it will not be done again below)
          }

        // Initialise forms...
        for (Form form : project.getForms()) {
          try {
            // generates Schema, Columns & ValueDictionaries:
            form.initialiseStorage(
                fsiProvider != null
                    ? fsiProvider.getByPassableFieldIDs(form)
                    : null); // Note: fsiProvider will be null if this project is loaded/parsed for
            // the first time
          } catch (ModelFullException mfe) {
            throw new SAXException(
                "This project contains more data-producing Forms than allowed (maximum: "
                    + Project.MAX_RECORD_PRODUCING_FORMS
                    + ").");
          } catch (DuplicateColumnException dce) {
            throw new SAXException(
                "Duplicate column name (\""
                    + dce.getColumnName()
                    + "\") in schema for form \""
                    + form.id
                    + "\".");
          }
          addWarnings(form.getWarnings()); // !!!
          form.clearWarnings();
        }
        // Seal project model:
        project.getModel().seal();

        // Resolve relationship constraints:
        if (relationshipToConstraints != null)
          for (Entry<Relationship, List<ConstraintDescription>> entry :
              relationshipToConstraints.entrySet())
            for (ConstraintDescription constrDesc : entry.getValue())
              try {
                Relationship rel = entry.getKey();
                rel.addConstraint(constrDesc.resolve(rel.getRelatedForm()));
              } catch (Exception e) {
                throw new Exception(
                    "Error upon resolving constraint on Relationship \"" + entry.getKey().id + "\"",
                    e);
              }
      }
    }
  }

  /** @return the format */
  protected Format getFormat() {
    return format;
  }

  /** @return the project */
  protected Project getProject() {
    return project;
  }

  /**
   * Called from {@link FormParser}
   *
   * @param relationship
   * @param formID
   */
  /*package*/ void addRelationship(Relationship relationship, String formID) {
    if (relationshipToFormID == null) relationshipToFormID = new HashMap<Relationship, String>();
    relationshipToFormID.put(relationship, formID);
  }

  /**
   * Called from {@link FormParser}
   *
   * @param relationship
   * @param columnName
   * @param comparisonAttrib
   * @param valueString
   * @throws Exception
   */
  /*package*/ void addRelationshipConstraint(
      Relationship relationship, String columnName, String comparisonAttrib, String valueString)
      throws Exception {
    if (relationshipToConstraints == null)
      relationshipToConstraints = new HashMap<Relationship, List<ConstraintDescription>>();
    if (!relationshipToConstraints.containsKey(relationship))
      relationshipToConstraints.put(relationship, new ArrayList<ConstraintDescription>());
    // Add constraint description to be resolved later:
    try {
      relationshipToConstraints
          .get(relationship)
          .add(new ConstraintDescription(columnName, comparisonAttrib, valueString));
    } catch (ParseException pe) {
      throw new Exception("Error upon parsing constraint", pe);
    }
  }

  /**
   * Helper class to temporarily hold descriptions of constraints (for now only of {@link
   * RuleConstraints}), until they can be resolved to Constraint instances
   *
   * @author mstevens
   */
  private class ConstraintDescription {

    String columnName;
    RuleConstraint.Comparison comparison;
    String valueString;

    /**
     * @param columnName
     * @param comparison
     * @param valueString
     * @throws ParseException
     */
    public ConstraintDescription(String columnName, String comparisonAttrib, String valueString)
        throws ParseException {
      this.columnName = columnName;
      this.valueString = valueString;
      this.comparison = RuleConstraint.parseComparisonString(comparisonAttrib);
    }

    public Constraint resolve(Form form)
        throws SAXException, IllegalArgumentException, NullPointerException, ParseException {
      // Form checks:
      if (form == null)
        throw new NullPointerException("Non-null form is needed to resolve Constraint");
      if (!form.isProducesRecords()) {
        addWarning(
            "Cannot impose constraint on records of form \""
                + form.id
                + "\" because it does not produce data records.");
        return null;
      }

      // Get column:
      @SuppressWarnings("unchecked")
      ColumnPointer<ComparableColumn<?>> columnPointer =
          (ColumnPointer<ComparableColumn<?>>)
              ColumnPointer.ByName(
                  form.getSchema(),
                  columnName,
                  true,
                  true); // will throw IllegalArgumentException if no such column is found (but name
      // sanitation will be used first)

      // Column check:
      if (!(columnPointer.getColumn() instanceof ComparableColumn<?>))
        throw new SAXException(
            "Constraint refers to a column (\"" + columnName + "\") which is not comparable.");

      // Return RuleConstraint:
      return RuleConstraint.FromString(columnPointer, comparison, valueString);
    }
  }

  /**
   * Called from {@link FormParser}
   *
   * @param task
   */
  /*package*/ void addPostProcessingTask(PostProcessTask task) {
    if (postProcessingTasks == null) postProcessingTasks = new ArrayList<PostProcessTask>();
    postProcessingTasks.add(task);
  }

  /** @return the postLoadingTasks */
  public List<PostProcessTask> getPostProcessingTasks() {
    return postProcessingTasks != null
        ? postProcessingTasks
        : Collections.<PostProcessTask>emptyList();
  }
}
예제 #4
0
 public static Format getFormat(String mimeType) {
   for (Format curSection : Format.values()) {
     if (curSection.mimeType.equals(mimeType)) return curSection;
   }
   throw new IllegalArgumentException("No such instance <" + mimeType + ">.");
 }