001/*
002 * The MIT License
003 * Copyright (c) 2012 Microsoft Corporation
004 *
005 * Permission is hereby granted, free of charge, to any person obtaining a copy
006 * of this software and associated documentation files (the "Software"), to deal
007 * in the Software without restriction, including without limitation the rights
008 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
009 * copies of the Software, and to permit persons to whom the Software is
010 * furnished to do so, subject to the following conditions:
011 *
012 * The above copyright notice and this permission notice shall be included in
013 * all copies or substantial portions of the Software.
014 *
015 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
016 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
017 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
018 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
019 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
020 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
021 * THE SOFTWARE.
022 */
023
024package microsoft.exchange.webservices.data.property.complex;
025
026import microsoft.exchange.webservices.data.attribute.EditorBrowsable;
027import microsoft.exchange.webservices.data.core.EwsServiceXmlReader;
028import microsoft.exchange.webservices.data.core.EwsServiceXmlWriter;
029import microsoft.exchange.webservices.data.core.ICustomXmlUpdateSerializer;
030import microsoft.exchange.webservices.data.core.XmlAttributeNames;
031import microsoft.exchange.webservices.data.core.XmlElementNames;
032import microsoft.exchange.webservices.data.core.service.ServiceObject;
033import microsoft.exchange.webservices.data.core.enumeration.attribute.EditorBrowsableState;
034import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace;
035import microsoft.exchange.webservices.data.property.definition.PropertyDefinition;
036
037import java.util.ArrayList;
038import java.util.HashMap;
039import java.util.List;
040import java.util.Map;
041import java.util.Map.Entry;
042
043/**
044 * Represents a generic dictionary that can be sent to or retrieved from EWS.
045 * TKey The type of key. TEntry The type of entry.
046 *
047 * @param <TKey>   the generic type
048 * @param <TEntry> the generic type
049 */
050@EditorBrowsable(state = EditorBrowsableState.Never)
051public abstract class DictionaryProperty
052    <TKey, TEntry extends DictionaryEntryProperty<TKey>>
053    extends ComplexProperty implements ICustomXmlUpdateSerializer, IComplexPropertyChangedDelegate<TEntry> {
054
055  /**
056   * The entries.
057   */
058  private Map<TKey, TEntry> entries = new HashMap<TKey, TEntry>();
059
060  /**
061   * The removed entries.
062   */
063  private Map<TKey, TEntry> removedEntries = new HashMap<TKey, TEntry>();
064
065  /**
066   * The added entries.
067   */
068  private List<TKey> addedEntries = new ArrayList<TKey>();
069
070  /**
071   * The modified entries.
072   */
073  private List<TKey> modifiedEntries = new ArrayList<TKey>();
074
075  /**
076   * Entry was changed.
077   *
078   * @param complexProperty the complex property
079   */
080  private void entryChanged(final TEntry complexProperty) {
081    TKey key = complexProperty.getKey();
082
083    if (!this.addedEntries.contains(key) && !this.modifiedEntries.contains(key)) {
084      this.modifiedEntries.add(key);
085      this.changed();
086    }
087  }
088
089  /**
090   * Writes the URI to XML.
091   *
092   * @param writer the writer
093   * @param key    the key
094   * @throws Exception the exception
095   */
096  private void writeUriToXml(EwsServiceXmlWriter writer, TKey key)
097      throws Exception {
098    writer.writeStartElement(XmlNamespace.Types,
099        XmlElementNames.IndexedFieldURI);
100    writer.writeAttributeValue(XmlAttributeNames.FieldURI, this
101        .getFieldURI());
102    writer.writeAttributeValue(XmlAttributeNames.FieldIndex, this
103        .getFieldIndex(key));
104    writer.writeEndElement();
105  }
106
107  /**
108   * Gets the index of the field.
109   *
110   * @param key the key
111   * @return Key index.
112   */
113  protected String getFieldIndex(TKey key) {
114    return key.toString();
115  }
116
117  /**
118   * Gets the field URI.
119   *
120   * @return Field URI.
121   */
122  protected String getFieldURI() {
123    return null;
124  }
125
126  /**
127   * Creates the entry.
128   *
129   * @param reader the reader
130   * @return Dictionary entry.
131   */
132  protected TEntry createEntry(EwsServiceXmlReader reader) {
133    if (reader.getLocalName().equalsIgnoreCase(XmlElementNames.Entry)) {
134      return this.createEntryInstance();
135    } else {
136      return null;
137    }
138  }
139
140  /**
141   * Creates instance of dictionary entry.
142   *
143   * @return New instance.
144   */
145  protected abstract TEntry createEntryInstance();
146
147  /**
148   * Gets the name of the entry XML element.
149   *
150   * @param entry the entry
151   * @return XML element name.
152   */
153  protected String getEntryXmlElementName(TEntry entry) {
154    return XmlElementNames.Entry;
155  }
156
157  /**
158   * Clears the change log.
159   */
160  public void clearChangeLog() {
161    this.addedEntries.clear();
162    this.removedEntries.clear();
163    this.modifiedEntries.clear();
164
165    for (TEntry entry : this.entries.values()) {
166      entry.clearChangeLog();
167    }
168  }
169
170  /**
171   * Add entry.
172   *
173   * @param entry the entry
174   */
175  protected void internalAdd(TEntry entry) {
176    entry.addOnChangeEvent(this);
177
178    this.entries.put(entry.getKey(), entry);
179    this.addedEntries.add(entry.getKey());
180    this.removedEntries.remove(entry.getKey());
181
182    this.changed();
183  }
184
185  /**
186   * Complex property changed.
187   *
188   * @param complexProperty accepts ComplexProperty
189   */
190  @Override
191  public void complexPropertyChanged(final TEntry complexProperty) {
192    entryChanged(complexProperty);
193  }
194
195  /**
196   * Add or replace entry.
197   *
198   * @param entry the entry
199   */
200  protected void internalAddOrReplace(TEntry entry) {
201    TEntry oldEntry;
202    if (this.entries.containsKey(entry.getKey())) {
203      oldEntry = this.entries.get(entry.getKey());
204      oldEntry.removeChangeEvent(this);
205
206      entry.addOnChangeEvent(this);
207
208      if (!this.addedEntries.contains(entry.getKey())) {
209        if (!this.modifiedEntries.contains(entry.getKey())) {
210          this.modifiedEntries.add(entry.getKey());
211        }
212      }
213
214      this.changed();
215    } else {
216      this.internalAdd(entry);
217    }
218  }
219
220  /**
221   * Remove entry based on key.
222   *
223   * @param key the key
224   */
225  protected void internalRemove(TKey key) {
226    TEntry entry;
227    if (this.entries.containsKey(key)) {
228      entry = this.entries.get(key);
229      entry.removeChangeEvent(this);
230
231      this.entries.remove(key);
232      this.removedEntries.put(key, entry);
233
234      this.changed();
235    }
236
237    this.addedEntries.remove(key);
238  }
239
240  /**
241   * Loads from XML.
242   *
243   * @param reader           the reader
244   * @param localElementName the local element name
245   * @throws Exception the exception
246   */
247  public void loadFromXml(EwsServiceXmlReader reader, String localElementName) throws Exception {
248    reader.ensureCurrentNodeIsStartElement(XmlNamespace.Types,
249        localElementName);
250
251    if (!reader.isEmptyElement()) {
252      do {
253        reader.read();
254
255        if (reader.isStartElement()) {
256          TEntry entry = this.createEntry(reader);
257
258          if (entry != null) {
259            entry.loadFromXml(reader, reader.getLocalName());
260            this.internalAdd(entry);
261          } else {
262            reader.skipCurrentElement();
263          }
264        }
265      } while (!reader.isEndElement(XmlNamespace.Types,
266          localElementName));
267    } else {
268      reader.read();
269    }
270  }
271
272  /**
273   * Writes to XML.
274   *
275   * @param writer         The writer
276   * @param xmlNamespace   The XML namespace.
277   * @param xmlElementName Name of the XML element.
278   * @throws Exception
279   */
280  @Override public void writeToXml(EwsServiceXmlWriter writer, XmlNamespace xmlNamespace,
281      String xmlElementName) throws Exception {
282    //  Only write collection if it has at least one element.
283    if (this.entries.size() > 0) {
284      super.writeToXml(
285          writer,
286          xmlNamespace,
287          xmlElementName);
288    }
289  }
290
291  /**
292   * Writes elements to XML.
293   *
294   * @param writer the writer
295   * @throws Exception the exception
296   */
297  public void writeElementsToXml(EwsServiceXmlWriter writer)
298      throws Exception {
299    for (Entry<TKey, TEntry> keyValuePair : this.entries.entrySet()) {
300      keyValuePair.getValue().writeToXml(writer,
301          this.getEntryXmlElementName(keyValuePair.getValue()));
302    }
303  }
304
305  /**
306   * Gets the entries.
307   *
308   * @return The entries.
309   */
310  protected Map<TKey, TEntry> getEntries() {
311    return entries;
312  }
313
314  /**
315   * Determines whether this instance contains the specified key.
316   *
317   * @param key the key
318   * @return true if this instance contains the specified key; otherwise,
319   * false.
320   */
321  public boolean contains(TKey key) {
322    return this.entries.containsKey(key);
323  }
324
325  /**
326   * Writes updates to XML.
327   *
328   * @param writer             the writer
329   * @param ewsObject          the ews object
330   * @param propertyDefinition the property definition
331   * @return True if property generated serialization.
332   * @throws Exception the exception
333   */
334  public boolean writeSetUpdateToXml(EwsServiceXmlWriter writer,
335      ServiceObject ewsObject, PropertyDefinition propertyDefinition)
336      throws Exception {
337    List<TEntry> tempEntries = new ArrayList<TEntry>();
338
339    for (TKey key : this.addedEntries) {
340      tempEntries.add(this.entries.get(key));
341    }
342    for (TKey key : this.modifiedEntries) {
343      tempEntries.add(this.entries.get(key));
344    }
345    for (TEntry entry : tempEntries) {
346
347      if (!entry.writeSetUpdateToXml(writer, ewsObject,
348          propertyDefinition.getXmlElement())) {
349        writer.writeStartElement(XmlNamespace.Types, ewsObject
350            .getSetFieldXmlElementName());
351        this.writeUriToXml(writer, entry.getKey());
352
353        writer.writeStartElement(XmlNamespace.Types, ewsObject
354            .getXmlElementName());
355        //writer.writeStartElement(XmlNamespace.Types, propertyDefinition.getXmlElementName());
356        writer.writeStartElement(XmlNamespace.Types, propertyDefinition.getXmlElement());
357        entry.writeToXml(writer, this.getEntryXmlElementName(entry));
358        writer.writeEndElement();
359        writer.writeEndElement();
360
361        writer.writeEndElement();
362      }
363    }
364
365    for (TEntry entry : this.removedEntries.values()) {
366      if (!entry.writeDeleteUpdateToXml(writer, ewsObject)) {
367        writer.writeStartElement(XmlNamespace.Types, ewsObject
368            .getDeleteFieldXmlElementName());
369        this.writeUriToXml(writer, entry.getKey());
370        writer.writeEndElement();
371      }
372    }
373
374    return true;
375  }
376
377  /**
378   * Writes deletion update to XML.
379   *
380   * @param writer    the writer
381   * @param ewsObject the ews object
382   * @return True if property generated serialization.
383   */
384  public boolean writeDeleteUpdateToXml(EwsServiceXmlWriter writer,
385      ServiceObject ewsObject) {
386    return false;
387  }
388}