protected void processProfilesMetadata(Element appSchemaElement) {

    Set<Info> schemaElements = new TreeSet<Info>();

    // identify all classes and properties that belong to the schema
    SortedSet<ClassInfo> schemaClasses = model.classes(schemaPi);

    if (schemaClasses != null) {

      for (ClassInfo ci : schemaClasses) {
        schemaElements.add(ci);

        for (PropertyInfo pi : ci.properties().values()) {
          schemaElements.add(pi);
        }
      }
    }

    // identify the profiles of all relevant schema elements
    SortedSet<String> profileNames = new TreeSet<String>();

    for (Info i : schemaElements) {

      String[] profilesTVs = i.taggedValuesForTag("profiles");

      for (String profilesTV : profilesTVs) {

        if (profilesTV.trim().length() > 0) {

          try {

            ProfileIdentifierMap piMap =
                ProfileIdentifierMap.parse(profilesTV, IdentifierPattern.loose, i.name());

            profileNames.addAll(piMap.getProfileIdentifiersByName().keySet());

          } catch (MalformedProfileIdentifierException e) {
            result.addWarning(this, 9, profilesTV, i.fullNameInSchema(), e.getMessage());
          }
        }
      }
    }

    // now create the ProfilesMetadata XML element
    Element e_pm = document.createElement("ProfilesMetadata");

    for (String name : profileNames) {
      Element e_cp = document.createElement("containedProfile");
      e_cp.setTextContent(name);
      e_pm.appendChild(e_cp);
    }

    Element e_m = document.createElement("metadata");

    e_m.appendChild(e_pm);

    appSchemaElement.appendChild(e_m);
  }
  @Override
  public void initialise(PackageInfo p, Model m, Options o, ShapeChangeResult r, boolean diagOnly)
      throws ShapeChangeAbortException {

    schemaPi = p;
    schemaTargetNamespace = p.targetNamespace();

    model = m;
    options = o;
    result = r;
    diagnosticsOnly = diagOnly;

    result.addDebug(this, 1, schemaPi.name());

    if (!initialised) {
      initialised = true;

      outputDirectory = options.parameter(this.getClass().getName(), "outputDirectory");
      if (outputDirectory == null) outputDirectory = options.parameter("outputDirectory");
      if (outputDirectory == null) outputDirectory = options.parameter(".");

      outputFilename = "schema_metadata.xml";

      // Check if we can use the output directory; create it if it
      // does not exist
      outputDirectoryFile = new File(outputDirectory);
      boolean exi = outputDirectoryFile.exists();
      if (!exi) {
        try {
          FileUtils.forceMkdir(outputDirectoryFile);
        } catch (IOException e) {
          result.addError(null, 600, e.getMessage());
          e.printStackTrace(System.err);
        }
        exi = outputDirectoryFile.exists();
      }
      boolean dir = outputDirectoryFile.isDirectory();
      boolean wrt = outputDirectoryFile.canWrite();
      boolean rea = outputDirectoryFile.canRead();
      if (!exi || !dir || !wrt || !rea) {
        result.addFatalError(null, 601, outputDirectory);
        throw new ShapeChangeAbortException();
      }

      File outputFile = new File(outputDirectoryFile, outputFilename);

      // check if output file already exists - if so, attempt to delete it
      exi = outputFile.exists();
      if (exi) {

        result.addInfo(this, 3, outputFilename, outputDirectory);

        try {
          FileUtils.forceDelete(outputFile);
          result.addInfo(this, 4);
        } catch (IOException e) {
          result.addInfo(null, 600, e.getMessage());
          e.printStackTrace(System.err);
        }
      }

      // identify map entries defined in the target configuration
      List<ProcessMapEntry> mapEntries = options.getCurrentProcessConfig().getMapEntries();

      if (mapEntries != null) {

        for (ProcessMapEntry pme : mapEntries) {
          mapEntryByType.put(pme.getType(), pme);
        }
      }

      // reset processed flags on all classes in the schema
      for (Iterator<ClassInfo> k = model.classes(schemaPi).iterator(); k.hasNext(); ) {
        ClassInfo ci = k.next();
        ci.processed(getTargetID(), false);
      }

      // ======================================
      // Parse configuration parameters
      // ======================================

      // nothing to do at present

      // ======================================
      // Set up the document and create root
      // ======================================

      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
      dbf.setNamespaceAware(true);
      dbf.setValidating(true);
      dbf.setAttribute(Options.JAXP_SCHEMA_LANGUAGE, Options.W3C_XML_SCHEMA);
      DocumentBuilder db;
      try {
        db = dbf.newDocumentBuilder();
      } catch (ParserConfigurationException e) {
        result.addFatalError(null, 2);
        throw new ShapeChangeAbortException();
      }
      document = db.newDocument();

      root = document.createElementNS(NS, "ApplicationSchemaMetadata");
      document.appendChild(root);

      addAttribute(root, "xmlns", NS);

      hook = document.createComment("Created by ShapeChange - http://shapechange.net/");
      root.appendChild(hook);
    }

    // create elements documenting the application schema
    Element e_name = document.createElement("name");
    e_name.setTextContent(schemaPi.name());
    Element e_tns = document.createElement("targetNamespace");
    e_tns.setTextContent(schemaPi.targetNamespace());

    Element e_as = document.createElement("ApplicationSchema");
    e_as.appendChild(e_name);
    e_as.appendChild(e_tns);

    Element e_schema = document.createElement("schema");
    e_schema.appendChild(e_as);

    root.appendChild(e_schema);

    /*
     * Now compute relevant metadata.
     */
    processMetadata(e_as);
  }