Package com.google.gdata.data

Source Code of com.google.gdata.data.BaseEntry$AtomHandler$IdHandler

/* Copyright (c) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


package com.google.gdata.data;

import com.google.gdata.util.common.xml.XmlNamespace;
import com.google.gdata.util.common.xml.XmlWriter;
import com.google.gdata.util.common.xml.XmlWriter.Attribute;
import com.google.gdata.client.CoreErrorDomain;
import com.google.gdata.client.GDataProtocol;
import com.google.gdata.client.Service;
import com.google.gdata.util.EventSourceParser;
import com.google.gdata.util.Namespaces;
import com.google.gdata.util.NotModifiedException;
import com.google.gdata.util.ParseException;
import com.google.gdata.util.ParseUtil;
import com.google.gdata.util.ServiceException;
import com.google.gdata.util.XmlParser;
import com.google.gdata.util.XmlParser.ElementHandler;

import org.xml.sax.Attributes;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Vector;


/**
* The BaseEntry class is an abstract base class that defines the
* in-memory object model for GData entries.
* <p>
* It is capable of parsing the Atom XML for an {@code <atom:entry>}
* element as well as any contained Extension elements.  It can generate
* both Atom and RSS 2.0 representations of the entry from the object
* model.
* <p>
* The BaseEntry class implements the {@link Kind.Adaptable} interface, meaning
* it is possible to create new {@link Kind.Adaptor} subtypes that defines
* a custom extension model (and associated convenience APIs) for a BaseEntry
* subtypes that use Atom/RSS extensions to extend the content model for a
* particular type of data.
* <p>
* An {@link Kind.Adaptor} subclass of BaseEntry should do the following:
* <ul>
* <li>Include a {@link Kind.Term} annotation on the class declaration that
* defines the {@link Category} term value for the GData kind handled by the
* adaptor.</li>
* <li>Provide a constructor that takes a single BaseEntry parameter as an
* argument that is used when adapting a generic entry type to a more specific
* one.</li>
* <li>Implement the {@link Kind.Adaptor#declareExtensions(ExtensionProfile)}
* method and use it to declare the extension model for the adapted instance
* within the profile passed as a parameter.   This is used to auto-extend
* an extension profile when kind Category tags are found during parsing of
* content.</li>
* <li>Expose convenience APIs to retrieve and set extension attributes, with
* an implementions that delegates to {@link ExtensionPoint} methods to
* store/retrieve the extension data.
* </ul>
*
* Here is the Relax-NG schema that represents an Atom 1.0 entry:
* <pre>
* atomEntry =
*   element atom:entry {
*     atomCommonAttributes,
*     (atomAuthor*
*     & atomCategory*
*     & atomContent?
*     & atomContributor*
*     & atomId
*     & atomLink*
*     & atomPublished?
*     & atomRights?
*     & atomSource?
*     & atomSummary?
*     & atomTitle
*     & atomUpdated
*     & extensionElement*)
* </pre>
*
* @param <E> the entry type associated with the bound subtype.
* @see Kind.Adaptor
* @see Kind.Adaptable
*
*
*
*/
public abstract class BaseEntry<E extends BaseEntry>
    extends ExtensionPoint
    implements Kind.Adaptable, Kind.Adaptor, IEntry {


  /**
   * The EntryState class provides a simple structure that encapsulates
   * the attributes of an Atom entry that should be shared with a shallow
   * copy if the entry is adapted to a more specific BaseEntry
   * {@link Kind.Adaptor} subtypes.
   *
   * @see BaseEntry#BaseEntry(BaseEntry)
   */
  protected static class EntryState {

    /** Entry id. */
    public String id;

    /**
     * Version ID. This is a unique number representing this particular
     * entry. Every update changes the version ID (unless the update
     * doesn't modify anything, in which case it's permissible for
     * version ID to stay the same). Services are free to interpret this
     * string in the most convenient way. Some services may choose to use
     * a monotonically increasing sequence of version IDs. Other services
     * may compute a hash of entry properties and use that.
     * <p>
     * This property is only used for services to communicate the current
     * version ID back to the servlet. It is NOT set when entries are
     * parsed (either from requests or from arbitrary XML).
     */
    public String versionId;

    /**
     * Etag.  See RFC 2616, Section 3.11.
     * If there is no entity tag, this variable is null.
     * Etags are provided not only on top-level entries,
     * but also on entries within feeds (in the form of
     * a gd:etag attribute).
     */
    public String etag;

    /** Creation timestamp. Ignored on updates. */
    public DateTime published;

    /** Last updated timestamp. */
    public DateTime updated;
   
    /** Last edit timestamp */
    public DateTime edited;

    /** Categories of entry. */
    public HashSet<Category> categories = new HashSet<Category>();

    /** Title of entry. */
    public TextConstruct title;

    /** Summary of entry. */
    public TextConstruct summary;

    /** Rights of entry. */
    public TextConstruct rights;

    /** Content of entry. */
    public Content content;

    /** Links of entry. */
    public LinkedList<Link> links = new LinkedList<Link>();

    /** Authors of entry. */
    public LinkedList<Person> authors = new LinkedList<Person>();

    /** Contributors of entry. */
    public LinkedList<Person> contributors = new LinkedList<Person>();

    /** Source. */
    public Source source;

    /** Service. */
    public Service service;

    /** {code true} if the entry can be modified by a client. */
    public boolean canEdit = true;

    /**
     * Atom publication control status, which contains the draft status.
     */
    public PubControl pubControl;

    /** Adaptable helper. */
    public Kind.Adaptable adaptable = new Kind.AdaptableHelper();
  }

  /**
   * Basic state for this entry.   May be shared across multiple adapted
   * instances associated with the same logical entry.
   */
  protected EntryState state;

  /**
   * Constructs a new BaseEntry instance.
   */
  protected BaseEntry() {
    state = new EntryState();
  }

  // Locally cache the atomPub namespace value, which is dynamic based upon
  // the in-use version but constant for any instance.
  private XmlNamespace atomPubNs;
  private XmlNamespace getAtomPubNs() {
    if (atomPubNs == null) {
      atomPubNs = Namespaces.getAtomPubNs();
    }
    return atomPubNs;
  }
 
  /**
   * Copy constructor that initializes a new BaseEntry instance to have
   * identical contents to another instance, using a shared reference to
   * the same {@link EntryState}{@link Kind.Adaptor} subclasses
   * of {@code BaseEntry} can use this constructor to create adaptor
   * instances of an entry that share state with the original.
   */
  protected BaseEntry(BaseEntry<?> sourceEntry) {
    super(sourceEntry);
    state = sourceEntry.state;
  }

  public String getId() { return state.id; }
  public void setId(String v) {
    if (v != null && "-".equals(v)) {
      // Disallow dash as an entry id. It leads to ambiguity because
      // we use a dash to separate category queries in a feed URI.
      // Does /feeds/feed-id/-/X mean a feed request with a category
      // query "X", or an entry request with "-" as the entry ID
      // and X as the version number. In {@link UriTemplate} we've
      // made the choice that it means a feed request. Therefore "-"
      // cannot be an entry ID.
      throw new IllegalArgumentException("Entry.id must not be equal to '-'.");
    }
    state.id = v;
  }

  public String getVersionId() { return state.versionId; }
  public void setVersionId(String v) { state.versionId = v; }

  public String getEtag() { return state.etag; }
  public void setEtag(String v) { state.etag = v; }

  public DateTime getPublished() { return state.published; }
  public void setPublished(DateTime v) {
    if (v != null && v.getTzShift() == null) {
      throw new IllegalArgumentException(
          "Entry.published must have a timezone.");
    }
    state.published = v;
  }

  public DateTime getUpdated() { return state.updated; }
  public void setUpdated(DateTime v) {
    if (v != null && v.getTzShift() == null) {
      throw new IllegalArgumentException("Entry.updated must have a timezone.");
    }
    state.updated = v;
  }
 
 
  public DateTime getEdited() { return state.edited; }
  public void setEdited(DateTime v) {
    if (v != null && v.getTzShift() == null) {
      throw new IllegalArgumentException("Entry.edited must have a timezone.");
    }
    state.edited = v;
  }
 
  public Set<Category> getCategories() { return state.categories; }

  public TextConstruct getTitle() { return state.title; }
  public void setTitle(TextConstruct v) { state.title = v; }

  public TextConstruct getSummary() { return state.summary; }
  public void setSummary(TextConstruct v) { state.summary = v; }

  public TextConstruct getRights() { return state.rights; }
  public void setRights(TextConstruct v) { state.rights = v; }

  public Content getContent() { return state.content; }
  public void setContent(Content v) { state.content = v; }

  /**
   * Assumes the content element's contents are text and
   * returns them as a TextContent.
   *
   * @return A TextContent containing the value of the content tag.
   *
   * @throws IllegalStateException
   *            If the content element is not a text type.
   */
  public TextContent getTextContent() {
    Content content = getContent();
    if (!(content instanceof TextContent)) {
      throw new IllegalStateException("Content object is not a TextContent");
    }
    return (TextContent) getContent();
  }

  /**
   * Assumes the <content> element's contents are plain-text and
   * returns its value as a string
   *
   * @return A string containing the plain-text value of the content tag.
   *
   * @throws IllegalStateException
   *            If the content element is not a text type.
   */
  public String getPlainTextContent() {
    TextConstruct textConstruct = getTextContent().getContent();
    if (!(textConstruct instanceof PlainTextConstruct)) {
      throw new IllegalStateException(
          "TextConstruct object is not a PlainTextConstruct");
    }
    return textConstruct.getPlainText();
  }

  public void setContent(TextConstruct tc) {
    state.content = new TextContent(tc);
  }

  public List<Link> getLinks() { return state.links; }

  public void addLink(Link link) {
    state.links.add(link);
  }
 
  public Link addLink(String rel, String type, String href) {
    Link link = new Link(rel, type, href);
    addLink(link);
    return link;
  }
 
  public List<Person> getAuthors() { return state.authors; }

  public List<Person> getContributors() { return state.contributors; }

  public Source getSource() { return state.source; }
  public void setSource(Source v) { state.source = v; }

  /**
   * Set draft status. Passing a null value means unsetting the draft
   * status.
   *
   * @param v   Draft status, or null to unset.
   */
  public void setDraft(Boolean v) {
    if (state.pubControl == null) {
      if (!Boolean.TRUE.equals(v)) {
        // No need to create a PubControl entry for that
        return;
      }
      state.pubControl = new PubControl();
    }
    state.pubControl.setDraft(v);
  }

  /**
   * Draft status.
   *
   * @return True if draft status is set and equals true.
   */
  public boolean isDraft() {
    return state.pubControl != null ? state.pubControl.isDraft() : false;
  }

  /**
   * Gets the app:control tag.
   *
   * @return pub control tag or null if unset
   */
  public PubControl getPubControl() { return state.pubControl; }

  /**
   * Sets the app:control tag, which usually contains app:draft.
   *
   * @param value PubControl the new object or null
   */
  public void setPubControl(PubControl value) { state.pubControl = value; }

  public void setService(Service s) { state.service = s; }
  public Service getService() { return state.service; }

  public boolean getCanEdit() { return state.canEdit; }
  public void setCanEdit(boolean v) { state.canEdit = v; }

  public void addAdaptor(Kind.Adaptor adaptor) {
    state.adaptable.addAdaptor(adaptor);
  }

  public Collection<Kind.Adaptor> getAdaptors() {
    return state.adaptable.getAdaptors();
  }

  public <A extends Kind.Adaptor> A getAdaptor(Class<A> adaptorClass) {
    return state.adaptable.getAdaptor(adaptorClass);
  }

  /**
   * Retrieves the first link with the supplied {@code rel} and/or
   * {@code type} value.
   * <p>
   * If either parameter is {@code null}, doesn't return matches
   * for that parameter.
   */
  public Link getLink(String rel, String type) {

    for (Link link : state.links) {
      if (link.matches(rel, type)) {
        return link;
      }
    }

    return null;
  }


  /**
   * Return the links that match the given {@code rel} and {@code type} values.
   *
   * @param relToMatch  {@code rel} value to match or {@code null} to match any
   *                    {@code rel} value.
   * @param typeToMatch {@code type} value to match or {@code null} to match any
   *                    {@code type} value.
   * @return matching links.
   */
  public List<Link> getLinks(String relToMatch, String typeToMatch) {
    List<Link> result = new ArrayList<Link>();
    for (Link link : state.links) {
      if (link.matches(relToMatch, typeToMatch)) {
        result.add(link);
      }
    }
    return result;
  }


  /**
   * Remove all links that match the given {@code rel} and {@code type} values.
   *
   * @param relToMatch  {@code rel} value to match or {@code null} to match any
   *                    {@code rel} value.
   * @param typeToMatch {@code type} value to match or {@code null} to match any
   *                    {@code type} value.
   */
  public void removeLinks(String relToMatch, String typeToMatch) {
    for (Iterator<Link> iterator = state.links.iterator();
        iterator.hasNext();) {
      Link link = iterator.next();
      if (link.matches(relToMatch, typeToMatch)) {
        iterator.remove();
      }
    }
  }


  /**
   * Adds a link pointing to an HTML representation.
   *
   * @param   htmlUri
   *            Link URI.
   *
   * @param   lang
   *            Optional language code.
   *
   * @param   title
   *            Optional title.
   */
  public void addHtmlLink(String htmlUri, String lang, String title) {

    Link link = new Link();
    link.setRel(Link.Rel.ALTERNATE);
    link.setType(Link.Type.HTML);
    link.setHref(htmlUri);

    if (lang != null) {
      link.setHrefLang(lang);
    }

    if (title != null) {
      link.setTitle(title);
    }

    state.links.add(link);
  }


  /** Retrieves the resource access link. */
  public Link getSelfLink() {
    Link selfLink = getLink(Link.Rel.SELF, Link.Type.ATOM);
    return selfLink;
  }


  /** Retrieves the resource edit link. */
  public Link getEditLink() {
    Link editLink = getLink(Link.Rel.ENTRY_EDIT, Link.Type.ATOM);
    return editLink;
  }


  /** Retrieves the media resource edit link. */
  @SuppressWarnings("deprecation")
  public Link getMediaEditLink() {
    Link mediaLink = getLink(Link.Rel.MEDIA_EDIT, null);
    if (mediaLink == null) {
      // Temporary back compat support for old incorrect media link value.
      // to the new value.
      mediaLink = getLink(Link.Rel.MEDIA_EDIT_BACKCOMPAT, null);
    }
    return mediaLink;
  }


  /** Retrieves the first HTML link. */
  public Link getHtmlLink() {
    Link htmlLink = getLink(Link.Rel.ALTERNATE, Link.Type.HTML);
    return htmlLink;
  }


  /**
   * Retrieves the current version of this Entry by requesting it from
   * the associated GData service.
   *
   * @return the current version of the entry.
   */
  public E getSelf() throws IOException, ServiceException {
    if (state.service == null) {
      throw new ServiceException(
          CoreErrorDomain.ERR.entryNotAssociated);
    }
    Link selfLink = getSelfLink();
    if (selfLink == null) {
      throw new UnsupportedOperationException("Entry cannot be retrieved");
    }
    URL entryUrl = new URL(selfLink.getHref());
    try {
      // If an etag is available, use it to conditionalize the retrieval,
      // otherwise, use time of last edit or update.
      if (state.etag != null) {
        return (E) state.service.getEntry(entryUrl, this.getClass(),
            state.etag);
      } else {
        return (E) state.service.getEntry(entryUrl, this.getClass(),
            (state.edited != null ? state.edited : state.updated));
      }
    } catch (NotModifiedException e) {
      return (E) this;
    }
  }


  /**
   * Updates this entry by sending the current representation to the
   * associated GData service.
   *
   * @return the updated entry returned by the Service.
   *
   * @throws ServiceException
   *           If there is no associated GData service or the service is
   *           unable to perform the update.
   *
   * @throws UnsupportedOperationException
   *           If update is not supported for the target entry.
   *
   * @throws IOException
   *           If there is an error communicating with the GData service.
   */
  public E update() throws IOException, ServiceException {

    if (state.service == null) {
      throw new ServiceException(
          CoreErrorDomain.ERR.entryNotAssociated);
    }
    Link editLink = getEditLink();
    if (editLink == null) {
      throw new UnsupportedOperationException("Entry cannot be updated");
    }
   
    URL editUrl = new URL(editLink.getHref());
    return (E) state.service.update(editUrl, this);
  }

  /**
   * Deletes this entry by sending a request to the associated GData
   * service.
   *
   * @throws ServiceException
   *           If there is no associated GData service or the service is
   *           unable to perform the deletion.
   *
   * @throws UnsupportedOperationException
   *           If deletion is not supported for the target entry.
   *
   * @throws IOException
   *           If there is an error communicating with the GData service.
   */
  public void delete() throws IOException, ServiceException {

    if (state.service == null) {
      throw new ServiceException(
          CoreErrorDomain.ERR.entryNotAssociated);
    }
    Link editLink = getEditLink();
    if (editLink == null) {
      throw new UnsupportedOperationException("Entry cannot be deleted");
    }
   
    // Delete the entry, using strong etag (if available) as a precondition.
    URL editUrl = new URL(editLink.getHref());
    state.service.delete(editUrl,
        GDataProtocol.isWeakEtag(state.etag) ? null : state.etag);
  }

  /**
   * The OutOfLineReference class adapts an {@link OutOfLineContent} instance
   * to implement the {@link Reference} interface so nested content references
   * will be post-processed.
   */
  private class OutOfLineReference implements Reference, Extension {
   
    // This ugliness is necessary because there's no unifying base abstraction
    // for all data elements and the current visitor model only acts upon
    // Extension types (which Content is not). This is fixed in the new data
    // model, at which time we can just have OolContent be visited and wrap it
    // in something that implement the Reference interface inside of
    // ReferenceVisitor.visit().
    private OutOfLineContent oolContent;
   
    private OutOfLineReference(OutOfLineContent oolContent) {
      this.oolContent = oolContent;
    }

    public String getHref() {
      return oolContent.getUri();
    }

    public void setHref(String href) {
      oolContent.setUri(href);
    }

    public void generate(XmlWriter w, ExtensionProfile extProfile) {
      throw new IllegalStateException("Should not be generated");
    }

    public ElementHandler getHandler(ExtensionProfile extProfile,
        String namespace, String localName, Attributes attrs) {
      throw new IllegalStateException("Should not be parsed");
    }
  }
 
  @Override
  protected void visitChildren(ExtensionVisitor ev)
      throws ExtensionVisitor.StoppedException {
   
    // Add out of line content to the visitor pattern by wrapping in an
    // adaptor.  This is necessary so the src reference can be processed.
    if (state.content instanceof OutOfLineContent) {
      this.visitChild(ev,
          new OutOfLineReference((OutOfLineContent) state.content));
    }
   
    // Add nested links to the visitor pattern
    for (Link link : getLinks()) {
      this.visitChild(ev, link);
    }
    super.visitChildren(ev);
  }

  @Override
  public void generate(XmlWriter w, ExtensionProfile p) throws IOException {
    generateAtom(w, p);
  }
 
  /**
   * Generates XML in the Atom format.
   *
   * @param   w
   *            Output writer.
   *
   * @param   extProfile
   *            Extension profile.
   *
   * @throws  IOException
   */
  public void generateAtom(XmlWriter w, ExtensionProfile extProfile)
      throws IOException {

    Set<XmlNamespace> nsDecls =
      new LinkedHashSet<XmlNamespace>(namespaceDeclsAtom);
    nsDecls.addAll(extProfile.getNamespaceDecls());

    ArrayList<XmlWriter.Attribute> attrs =
      new ArrayList<XmlWriter.Attribute>(3);

    if (state.etag != null &&
        !Service.getVersion().isCompatible(Service.Versions.V1)) {
      nsDecls.add(Namespaces.gNs);
      attrs.add(new XmlWriter.Attribute(Namespaces.gAlias, "etag", state.etag));
    }
   
    // Add any attribute extensions.
    AttributeGenerator generator = new AttributeGenerator();
    putAttributes(generator);
    generateAttributes(attrs, generator);
   
    generateStartElement(w, Namespaces.atomNs, "entry", attrs, nsDecls);

    if (state.id != null) {
      w.simpleElement(Namespaces.atomNs, "id", null, state.id);
    }

    if (state.published != null) {
      w.simpleElement(Namespaces.atomNs, "published", null,
                      state.published.toString());
    }

    if (state.updated != null) {
      w.simpleElement(Namespaces.atomNs, "updated", null,
          state.updated.toString());
    }
   
    if (state.edited != null) {
      w.simpleElement(getAtomPubNs(), "edited", null,
          state.edited.toString());
    }

    if (state.pubControl != null) {
      state.pubControl.generateAtom(w, extProfile);
    }

    w.startRepeatingElement();
    for (Category cat : state.categories) {
      cat.generateAtom(w);
    }
    w.endRepeatingElement();

    if (state.title != null) {
      state.title.generateAtom(w, "title");
    }

    if (state.summary != null) {
      state.summary.generateAtom(w, "summary");
    }

    if (state.rights != null) {
      state.rights.generateAtom(w, "rights");
    }

    if (state.content != null) {
      state.content.generateAtom(w, extProfile);
    }

    w.startRepeatingElement();
    for (Link link : state.links) {
      link.generateAtom(w, extProfile);
    }
    w.endRepeatingElement();

    w.startRepeatingElement();
    for (Person author : state.authors) {
      author.generateAtom(extProfile, w, "author");
    }
    w.endRepeatingElement();

    w.startRepeatingElement();
    for (Person contributor : state.contributors) {
      contributor.generateAtom(extProfile, w, "contributor");
    }
    w.endRepeatingElement();

    if (state.source != null) {
      state.source.generateAtom(w, extProfile);
    }

    // Invoke ExtensionPoint.
    generateExtensions(w, extProfile);

    w.endElement(Namespaces.atomNs, "entry");
  }


  /**
   * Generates XML in the RSS format.
   *
   * @param   w
   *            Output writer.
   *
   * @param   extProfile
   *            Extension profile.
   *
   * @throws  IOException
   */
  public void generateRss(XmlWriter w,
                          ExtensionProfile extProfile) throws IOException {

    Vector<XmlNamespace> nsDecls = new Vector<XmlNamespace>(namespaceDeclsRss);
    nsDecls.addAll(extProfile.getNamespaceDecls());

    generateStartElement(w, Namespaces.rssNs, "item", null, nsDecls);

    if (state.id != null) {
      List<Attribute> attrs = new ArrayList<Attribute>(1);
      attrs.add(new Attribute("isPermaLink", "false"));
      w.simpleElement(Namespaces.rssNs, "guid", attrs, state.id);
    }

    String lang = null;

    if (state.content != null) {
      lang = state.content.getLang();
    }

    if (lang == null && state.summary != null) {
      lang = state.summary.getLang();
    }

    if (lang == null && state.title != null) {
      lang = state.title.getLang();
    }

    if (lang != null) {
      w.simpleElement(Namespaces.rssNs, "language", null, lang);
    }

    if (state.published != null) {
      w.simpleElement(Namespaces.rssNs, "pubDate", null,
                      state.published.toStringRfc822());
    }

    if (state.updated != null) {
      w.simpleElement(Namespaces.atomNs, "updated", null,
          state.updated.toString());
    }

    w.startRepeatingElement();
    for (Category cat : state.categories) {
      cat.generateRss(w);
    }
    w.endRepeatingElement();

    if (state.title != null) {
      state.title.generateRss(w, "title", TextConstruct.RssFormat.PLAIN_TEXT);
    }

    if (state.summary != null) {
      state.summary.generateAtom(w, "summary");
    }

    if (state.content != null) {
      state.content.generateRss(w, extProfile);
    }

    w.startRepeatingElement();
    for (Link link : state.links) {
      link.generateRss(w);
    }
    w.endRepeatingElement();

    w.startRepeatingElement();
    for (Person author : state.authors) {
      author.generateRss(w, "author");
    }
    w.endRepeatingElement();

    w.startRepeatingElement();
    for (Person contributor : state.contributors) {
      contributor.generateRss(w, "author");
    }
    w.endRepeatingElement();

    // Invoke ExtensionPoint.
    generateExtensions(w, extProfile);

    w.endElement(Namespaces.rssNs, "item");
  }


  /** Top-level namespace declarations for generated XML. */
  private static final Collection<XmlNamespace> namespaceDeclsAtom =
    new Vector<XmlNamespace>(1);

  private static final Collection<XmlNamespace> namespaceDeclsRss =
    new Vector<XmlNamespace>(1);

  static {
    namespaceDeclsAtom.add(Namespaces.atomNs);
    namespaceDeclsRss.add(Namespaces.atomNs);
  }

  /**
   * Reads an entry representation from the provided {@link ParseSource}.
   * The return type of the entry will be determined using dynamic adaptation
   * based upon any {@link Kind} category tag found in the input content. If
   * no kind tag is found an {@link Entry} instance will be returned.
   */
  public static BaseEntry<?> readEntry(ParseSource source)
      throws IOException, ParseException, ServiceException {
    return readEntry(source, null, null);
  }

  /**
   * Reads an entry of type {@code T} using the given extension profile.
   */
  public static <T extends BaseEntry> T readEntry(ParseSource source,
      Class <T> entryClass, ExtensionProfile extProfile)
      throws IOException, ParseException, ServiceException {
    return ParseUtil.readEntry(source, entryClass, extProfile);
  }

  /**
   * Parses XML in the Atom format.
   *
   * @param   extProfile
   *            Extension profile.
   *
   * @param   input
   *            XML input stream.
   */
  public void parseAtom(ExtensionProfile extProfile,
                        InputStream input) throws IOException,
                                              ParseException {

    AtomHandler handler = new AtomHandler(extProfile);
    new XmlParser().parse(input, handler, Namespaces.atom, "entry");
  }


  /**
   * Parses XML in the Atom format.
   *
   * @param   extProfile
   *            Extension profile.
   *
   * @param   reader
   *            XML Reader.  The caller is responsible for ensuring
   *            that the character encoding is correct.
   */
  public void parseAtom(ExtensionProfile extProfile,
                        Reader reader) throws IOException,
                                              ParseException {

    AtomHandler handler = new AtomHandler(extProfile);
    new XmlParser().parse(reader, handler, Namespaces.atom, "entry");
  }


  /**
   * Parses XML in the Atom format from a parser-defined content source.
   *
   * @param   extProfile
   *            Extension profile.
   * @param   eventSource
   *            XML event source.
   */
  public void parseAtom(ExtensionProfile extProfile,
      XmlEventSource eventSource) throws IOException, ParseException {

    AtomHandler handler = new AtomHandler(extProfile);
    new EventSourceParser(handler, Namespaces.atom, "entry")
        .parse(eventSource);
  }

  /** Returns information about the content element processing. */
  protected Content.ChildHandlerInfo getContentHandlerInfo(
      ExtensionProfile extProfile, Attributes attrs)
      throws ParseException, IOException {
    return Content.getChildHandler(extProfile, attrs);
  }

  @Override
  public ElementHandler getHandler(ExtensionProfile p, String namespace,
      String localName, Attributes attrs) {
    return new AtomHandler(p);
  }
 
  /** {@code <atom:entry>} parser. */
  public class AtomHandler extends ExtensionPoint.ExtensionHandler {

    public AtomHandler(ExtensionProfile extProfile) {
      super(extProfile, BaseEntry.this.getClass());
    }

    @Override
    public void processAttribute(String namespace, String localName,
        String value) throws ParseException {
      if (namespace.equals(Namespaces.g)) {
        if (localName.equals("etag")) {
          setEtag(value);
          return;
        }
      }
      super.processAttribute(namespace, localName, value);
    }

    @Override
    public XmlParser.ElementHandler getChildHandler(String namespace,
                                                    String localName,
                                                    Attributes attrs)
        throws ParseException, IOException {


      if (namespace.equals(Namespaces.atom)) {

        if (localName.equals("id")) {

          return new IdHandler();

        } else if (localName.equals("published")) {

          return new PublishedHandler();

        } else if (localName.equals("updated")) {

          return new UpdatedHandler();

        } else if (localName.equals("title")) {

          TextConstruct.ChildHandlerInfo chi =
            TextConstruct.getChildHandler(attrs);

          if (state.title != null) {
            throw new ParseException(
                CoreErrorDomain.ERR.duplicateTitle);
          }

          state.title = chi.textConstruct;
          return chi.handler;

        } else if (localName.equals("summary")) {

          TextConstruct.ChildHandlerInfo chi =
            TextConstruct.getChildHandler(attrs);

          if (state.summary != null) {
            throw new ParseException(
                CoreErrorDomain.ERR.duplicateSummary);
          }

          state.summary = chi.textConstruct;
          return chi.handler;

        } else if (localName.equals("rights")) {

          TextConstruct.ChildHandlerInfo chi =
            TextConstruct.getChildHandler(attrs);

          if (state.rights != null) {
            throw new ParseException(
                CoreErrorDomain.ERR.duplicateRights);
          }

          state.rights = chi.textConstruct;
          return chi.handler;

        } else if (localName.equals("content")) {

          if (state.content != null) {
            throw new ParseException(
                CoreErrorDomain.ERR.duplicateContent);
          }

          Content.ChildHandlerInfo chi =
              getContentHandlerInfo(extProfile, attrs);

          state.content = chi.content;
          return chi.handler;

        } else if (localName.equals("category")) {

          Category cat = new Category();
          return cat.new AtomHandler(extProfile, state.categories,
              BaseEntry.this);

        } else if (localName.equals("link")) {

          Link link = new Link();
          state.links.add(link);
          return link.new AtomHandler(extProfile);

        } else if (localName.equals("author")) {

          Person author = new Person();
          state.authors.add(author);
          return author.new AtomHandler(extProfile);

        } else if (localName.equals("contributor")) {

          Person contributor = new Person();
          state.contributors.add(contributor);
          return contributor.new AtomHandler(extProfile);

        } else if (localName.equals("source")) {

          state.source = new Source();
          return state.source.new SourceHandler(extProfile);

        }

      } else if (namespace.equals(getAtomPubNs().getUri())) {

        if (localName.equals("control")) {

          state.pubControl = new PubControl();
          return state.pubControl.new AtomHandler(extProfile);

        } else if (localName.equals("edited")) {

          return new EditedHandler();
         
        }

      } else {

        return super.getChildHandler(namespace, localName, attrs);

      }

      return null;
    }


    /** {@code <atom:id>} parser. */
    class IdHandler extends XmlParser.ElementHandler {

      @Override
      public void processEndElement() throws ParseException {

        if (state.id != null) {
          throw new ParseException(
              CoreErrorDomain.ERR.duplicateEntryId);
        }

        if (value == null) {
          throw new ParseException(
              CoreErrorDomain.ERR.idValueRequired);
        }

        state.id = value;
      }
    }


    /** {@code <atom:published>} parser. */
    class PublishedHandler extends Rfc3339Handler {

      @Override
      public void processEndElement() throws ParseException {
        super.processEndElement();
        state.published = getDateTime();
      }
    }


    /** {@code <atom:updated>} parser. */
    class UpdatedHandler extends Rfc3339Handler {

      @Override
      public void processEndElement() throws ParseException {
        super.processEndElement();
        state.updated = getDateTime();
      }
    }

    /** {@code <app:edited>} parser. */
    class EditedHandler extends Rfc3339Handler {

      @Override
      public void processEndElement() throws ParseException {
        super.processEndElement();
        state.edited = getDateTime();
      }
    }
  }

  /**
   * Locates and returns the most specific {@link Kind.Adaptor} BaseEntry
   * subtype for this entry.  If none can be found for the current class,
   * {@code null} will be returned.
   *
   * @throws Kind.AdaptorException for subclasses to throw.
   */
  public BaseEntry<?> getAdaptedEntry() throws Kind.AdaptorException {

    BaseEntry<?> adaptedEntry = null;

    // Find the BaseEntry adaptor instance that is most specific.
    for (Kind.Adaptor adaptor : getAdaptors()) {
      if (!(adaptor instanceof BaseEntry)) {
        continue;
      }
      // If first matching adaptor or a narrower subtype of the current one,
      // then use it.
      if (adaptedEntry == null ||
          adaptedEntry.getClass().isAssignableFrom(adaptor.getClass())) {
        adaptedEntry = (BaseEntry<?>) adaptor;
      }
    }

    return adaptedEntry;
  }
}
TOP

Related Classes of com.google.gdata.data.BaseEntry$AtomHandler$IdHandler

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.