/**
  * Returns the matching character for the specified character.
  *
  * @param c a character occurring in a character pair
  * @return the matching character
  */
 public char getMatching(char c) {
   for (int i = 0; i < fPairs.length; i += 2) {
     if (getStartChar(i) == c) return getEndChar(i);
     else if (getEndChar(i) == c) return getStartChar(i);
   }
   Assert.isTrue(false);
   return '\0';
 }
  /**
   * Creates a new completion proposal. All fields are initialized based on the provided
   * information.
   *
   * @param replacementString the actual string to be inserted into the document
   * @param replacementOffset the offset of the text to be replaced
   * @param replacementLength the length of the text to be replaced
   * @param cursorPosition the position of the cursor following the insert relative to
   *     replacementOffset
   * @param image the image to display for this proposal
   * @param displayString the string to be displayed for the proposal
   * @param contextInformation the context information associated with this proposal
   * @param additionalProposalInfo the additional information associated with this proposal
   */
  public WaypointCompletionProposal(
      String replacementString,
      int replacementOffset,
      int replacementLength,
      int cursorPosition,
      Image image,
      String displayString,
      IContextInformation contextInformation,
      String additionalProposalInfo) {
    Assert.isNotNull(replacementString);
    Assert.isTrue(replacementOffset >= 0);
    Assert.isTrue(replacementLength >= 0);
    Assert.isTrue(cursorPosition >= 0);

    fReplacementString = replacementString;
    fReplacementOffset = replacementOffset;
    fReplacementLength = replacementLength;
    fCursorPosition = cursorPosition;
    fImage = image;
    fDisplayString = displayString;
    fContextInformation = contextInformation;
    fAdditionalProposalInfo = additionalProposalInfo;
  }
  /**
   * Saves the templates as XML.
   *
   * @param templates the templates to save
   * @param result the stream result to write to
   * @throws IOException if writing the templates fails
   */
  private void save(TemplatePersistenceData[] templates, StreamResult result) throws IOException {
    try {
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      DocumentBuilder builder = factory.newDocumentBuilder();
      Document document = builder.newDocument();

      Node root = document.createElement(TEMPLATE_ROOT);
      document.appendChild(root);

      for (int i = 0; i < templates.length; i++) {
        TemplatePersistenceData data = templates[i];
        Template template = data.getTemplate();

        Node node = document.createElement(TEMPLATE_ELEMENT);
        root.appendChild(node);

        NamedNodeMap attributes = node.getAttributes();

        String id = data.getId();
        if (id != null) {
          Attr idAttr = document.createAttribute(ID_ATTRIBUTE);
          idAttr.setValue(id);
          attributes.setNamedItem(idAttr);
        }

        if (template != null) {
          Attr name = document.createAttribute(NAME_ATTRIBUTE);
          name.setValue(template.getName());
          attributes.setNamedItem(name);
        }

        if (template != null) {
          Attr proposalDesc = document.createAttribute(PROPOSAL_POPUP_DESCRIPTION);
          if (template instanceof SQLTemplate) {
            proposalDesc.setValue(((SQLTemplate) template).getProposalPopupDescription());
          }
          attributes.setNamedItem(proposalDesc);
        }

        if (template != null) {
          Attr description = document.createAttribute(DESCRIPTION_ATTRIBUTE);
          description.setValue(template.getDescription());
          attributes.setNamedItem(description);
        }

        if (template != null) {
          Attr context = document.createAttribute(CONTEXT_ATTRIBUTE);
          context.setValue(template.getContextTypeId());
          attributes.setNamedItem(context);
        }

        Attr enabled = document.createAttribute(ENABLED_ATTRIBUTE);
        enabled.setValue(data.isEnabled() ? Boolean.toString(true) : Boolean.toString(false));
        attributes.setNamedItem(enabled);

        Attr deleted = document.createAttribute(DELETED_ATTRIBUTE);
        deleted.setValue(data.isDeleted() ? Boolean.toString(true) : Boolean.toString(false));
        attributes.setNamedItem(deleted);

        if (template != null) {
          Attr autoInsertable = document.createAttribute(AUTO_INSERTABLE_ATTRIBUTE);
          autoInsertable.setValue(
              template.isAutoInsertable() ? Boolean.toString(true) : Boolean.toString(false));
          attributes.setNamedItem(autoInsertable);
        }

        if (template != null) {
          Text pattern = document.createTextNode(template.getPattern());
          node.appendChild(pattern);
        }
      }

      Transformer transformer = TransformerFactory.newInstance().newTransformer();
      transformer.setOutputProperty(OutputKeys.METHOD, "xml"); // $NON-NLS-1$
      transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); // $NON-NLS-1$
      DOMSource source = new DOMSource(document);

      transformer.transform(source, result);

    } catch (ParserConfigurationException e) {
      Assert.isTrue(false);
    } catch (TransformerException e) {
      if (e.getException() instanceof IOException) throw (IOException) e.getException();
      Assert.isTrue(false);
    }
  }
  /**
   * Reads templates from an <code>InputSource</code> and adds them to the templates.
   *
   * @param source the input source
   * @param bundle a resource bundle to use for translating the read templates, or <code>null</code>
   *     if no translation should occur
   * @param singleId the template id to extract, or <code>null</code> to read in all templates
   * @return the read templates, encapsulated in instances of <code>TemplatePersistenceData</code>
   * @throws IOException if reading from the stream fails
   */
  private TemplatePersistenceData[] read(InputSource source, ResourceBundle bundle, String singleId)
      throws IOException {
    try {
      Collection templates = new ArrayList();
      Set ids = new HashSet();

      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      DocumentBuilder parser = factory.newDocumentBuilder();
      Document document = parser.parse(source);

      NodeList elements = document.getElementsByTagName(TEMPLATE_ELEMENT);

      int count = elements.getLength();
      for (int i = 0; i != count; i++) {
        Node node = elements.item(i);
        NamedNodeMap attributes = node.getAttributes();

        if (attributes == null) {
          continue;
        }

        String id = getStringValue(attributes, ID_ATTRIBUTE, null);

        if (id != null && ids.contains(id)) {
          throw new IOException(Messages.SQLTemplateReaderWriter_duplicate_id); // $NON-NLS-1$
        }

        if (singleId != null && !singleId.equals(id)) continue;

        boolean deleted = getBooleanValue(attributes, DELETED_ATTRIBUTE, false);

        String name = getStringValue(attributes, NAME_ATTRIBUTE);
        name = translateString(name, bundle);

        String proposalDesc = null;
        try {
          proposalDesc = getStringValue(attributes, PROPOSAL_POPUP_DESCRIPTION);
        } catch (SAXException e) {
          // ignore
        }
        if (proposalDesc != null) {
          proposalDesc = translateString(proposalDesc, bundle);
        }

        String description = getStringValue(attributes, DESCRIPTION_ATTRIBUTE, ""); // $NON-NLS-1$
        description = translateString(description, bundle);

        String context = getStringValue(attributes, CONTEXT_ATTRIBUTE);

        if (name == null || context == null) {
          throw new IOException(
              Messages.SQLTemplateReaderWriter_error_missing_attribute); // $NON-NLS-1$
        }

        boolean enabled = getBooleanValue(attributes, ENABLED_ATTRIBUTE, true);
        boolean autoInsertable = getBooleanValue(attributes, AUTO_INSERTABLE_ATTRIBUTE, true);

        StringBuffer buffer = new StringBuffer();
        NodeList children = node.getChildNodes();
        for (int j = 0; j != children.getLength(); j++) {
          String value = children.item(j).getNodeValue();
          if (value != null) {
            buffer.append(value);
          }
        }
        String pattern = buffer.toString();
        pattern = translateString(pattern, bundle);

        SQLTemplate template =
            new SQLTemplate(id, name, description, context, pattern, autoInsertable, proposalDesc);
        TemplatePersistenceData data = new TemplatePersistenceData(template, enabled, id);
        data.setDeleted(deleted);

        templates.add(data);

        if (singleId != null && singleId.equals(id)) {
          break;
        }
      }

      return (TemplatePersistenceData[])
          templates.toArray(new TemplatePersistenceData[templates.size()]);

    } catch (ParserConfigurationException e) {
      Assert.isTrue(false);
    } catch (SAXException e) {
      Throwable t = e.getCause();
      if (t instanceof IOException) {
        throw (IOException) t;
      } else if (t != null) {
        throw new IOException(t.getMessage());
      } else {
        throw new IOException(e.getMessage());
      }
    }

    return null; // dummy
  }