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    
024    package microsoft.exchange.webservices.data.property.complex;
025    
026    import microsoft.exchange.webservices.data.attribute.EditorBrowsable;
027    import microsoft.exchange.webservices.data.core.EwsServiceXmlReader;
028    import microsoft.exchange.webservices.data.core.EwsServiceXmlWriter;
029    import microsoft.exchange.webservices.data.core.EwsUtilities;
030    import microsoft.exchange.webservices.data.core.ICustomXmlUpdateSerializer;
031    import microsoft.exchange.webservices.data.core.service.ServiceObject;
032    import microsoft.exchange.webservices.data.core.enumeration.attribute.EditorBrowsableState;
033    import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace;
034    import microsoft.exchange.webservices.data.core.exception.service.local.ServiceLocalException;
035    import microsoft.exchange.webservices.data.property.definition.PropertyDefinition;
036    
037    import java.util.ArrayList;
038    import java.util.Iterator;
039    import java.util.List;
040    
041    /**
042     * Represents a collection of property that can be sent to and retrieved from
043     * EWS.
044     *
045     * @param <TComplexProperty> ComplexProperty type.
046     */
047    @EditorBrowsable(state = EditorBrowsableState.Never)
048    public abstract class ComplexPropertyCollection
049        <TComplexProperty extends ComplexProperty>
050        extends ComplexProperty implements ICustomXmlUpdateSerializer,
051        Iterable<TComplexProperty>, IComplexPropertyChangedDelegate<TComplexProperty> {
052    
053      /**
054       * The item.
055       */
056      private final List<TComplexProperty> items = new ArrayList<TComplexProperty>();
057    
058      /**
059       * The added item.
060       */
061      private final List<TComplexProperty> addedItems =
062          new ArrayList<TComplexProperty>();
063    
064      /**
065       * The modified item.
066       */
067      private final List<TComplexProperty> modifiedItems =
068          new ArrayList<TComplexProperty>();
069    
070      /**
071       * The removed item.
072       */
073      private final List<TComplexProperty> removedItems =
074          new ArrayList<TComplexProperty>();
075    
076      /**
077       * Creates the complex property.
078       *
079       * @param xmlElementName Name of the XML element.
080       * @return Complex property instance.
081       */
082      protected abstract TComplexProperty createComplexProperty(
083          String xmlElementName);
084    
085      /**
086       * Gets the name of the collection item XML element.
087       *
088       * @param complexProperty The complex property.
089       * @return XML element name.
090       */
091      protected abstract String getCollectionItemXmlElementName(
092          TComplexProperty complexProperty);
093    
094      /**
095       * Initializes a new instance of. ComplexPropertyCollection
096       */
097      protected ComplexPropertyCollection() {
098        super();
099      }
100    
101      /**
102       * Item changed.
103       *
104       * @param property The complex property.
105       */
106      protected void itemChanged(final TComplexProperty property) {
107        EwsUtilities.ewsAssert(
108          property != null, "ComplexPropertyCollection.ItemChanged",
109          "The complexProperty argument must be not null"
110        );
111    
112        if (!this.addedItems.contains(property)) {
113          if (!this.modifiedItems.contains(property)) {
114            this.modifiedItems.add(property);
115            this.changed();
116          }
117        }
118      }
119    
120      /**
121       * Loads from XML.
122       *
123       * @param reader           The reader.
124       * @param localElementName Name of the local element.
125       */
126      @Override public void loadFromXml(EwsServiceXmlReader reader, String localElementName) throws Exception {
127        this.loadFromXml(
128            reader,
129            XmlNamespace.Types,
130            localElementName);
131      }
132    
133      /**
134       * Loads from XML.
135       *
136       * @param reader           The reader.
137       * @param xmlNamespace     The XML namespace.
138       * @param localElementName Name of the local element.
139       */
140      @Override public void loadFromXml(EwsServiceXmlReader reader, XmlNamespace xmlNamespace,
141          String localElementName) throws Exception {
142        reader.ensureCurrentNodeIsStartElement(xmlNamespace,
143            localElementName);
144        if (!reader.isEmptyElement()) {
145          do {
146            reader.read();
147    
148            if (reader.isStartElement()) {
149              TComplexProperty complexProperty = this
150                  .createComplexProperty(reader.getLocalName());
151    
152              if (complexProperty != null) {
153                complexProperty.loadFromXml(reader, reader
154                    .getLocalName());
155                this.internalAdd(complexProperty, true);
156              } else {
157                reader.skipCurrentElement();
158              }
159            }
160          } while (!reader.isEndElement(xmlNamespace, localElementName));
161        } else {
162          reader.read();
163        }
164      }
165    
166      /**
167       * Loads from XML to update itself.
168       *
169       * @param reader         The reader.
170       * @param xmlNamespace   The XML namespace.
171       * @param xmlElementName Name of the XML element.
172       */
173      public void updateFromXml(
174          EwsServiceXmlReader reader,
175          XmlNamespace xmlNamespace,
176          String xmlElementName) throws Exception {
177        reader.ensureCurrentNodeIsStartElement(xmlNamespace, xmlElementName);
178    
179        if (!reader.isEmptyElement()) {
180          int index = 0;
181          do {
182            reader.read();
183    
184            if (reader.isStartElement()) {
185              TComplexProperty complexProperty = this.createComplexProperty(reader.getLocalName());
186              TComplexProperty actualComplexProperty = this.getPropertyAtIndex(index++);
187    
188              if (complexProperty == null || !complexProperty.equals(actualComplexProperty)) {
189                throw new ServiceLocalException("Property type incompatible when updating collection.");
190              }
191    
192              actualComplexProperty.updateFromXml(reader, xmlNamespace, reader.getLocalName());
193            }
194          }
195          while (!reader.isEndElement(xmlNamespace, xmlElementName));
196        }
197      }
198    
199      /**
200       * Writes to XML.
201       *
202       * @param writer         The writer.
203       * @param xmlNamespace   The XML namespace.
204       * @param xmlElementName Name of the XML element.
205       */
206      @Override public void writeToXml(EwsServiceXmlWriter writer, XmlNamespace xmlNamespace,
207          String xmlElementName) throws Exception {
208        if (this.shouldWriteToXml()) {
209          super.writeToXml(
210              writer,
211              xmlNamespace,
212              xmlElementName);
213        }
214      }
215    
216      /**
217       * Determine whether we should write collection to XML or not.
218       *
219       * @return True if collection contains at least one element.
220       */
221      public boolean shouldWriteToXml() {
222        //Only write collection if it has at least one element.
223        return this.getCount() > 0;
224      }
225    
226      /**
227       * Writes elements to XML.
228       *
229       * @param writer The writer.
230       * @throws Exception the exception
231       */
232      @Override
233      public void writeElementsToXml(EwsServiceXmlWriter writer)
234          throws Exception {
235        for (TComplexProperty complexProperty : this) {
236          complexProperty.writeToXml(writer, this
237              .getCollectionItemXmlElementName(complexProperty));
238        }
239      }
240    
241      /**
242       * Clears the change log.
243       */
244      @Override public void clearChangeLog() {
245        this.removedItems.clear();
246        this.addedItems.clear();
247        this.modifiedItems.clear();
248      }
249    
250      /**
251       * Removes from change log.
252       *
253       * @param complexProperty The complex property.
254       */
255      protected void removeFromChangeLog(TComplexProperty complexProperty) {
256        this.removedItems.remove(complexProperty);
257        this.modifiedItems.remove(complexProperty);
258        this.addedItems.remove(complexProperty);
259      }
260    
261      /**
262       * Gets the item.
263       *
264       * @return The item.
265       */
266      public List<TComplexProperty> getItems() {
267        return this.items;
268      }
269    
270      /**
271       * Gets the added item.
272       *
273       * @return The added item.
274       */
275      protected List<TComplexProperty> getAddedItems() {
276        return this.addedItems;
277      }
278    
279      /**
280       * Gets the modified item.
281       *
282       * @return The modified item.
283       */
284      protected List<TComplexProperty> getModifiedItems() {
285        return this.modifiedItems;
286      }
287    
288      /**
289       * Gets the removed item.
290       *
291       * @return The removed item.
292       */
293      protected List<TComplexProperty> getRemovedItems() {
294        return this.removedItems;
295      }
296    
297      /**
298       * Add complex property.
299       *
300       * @param complexProperty The complex property.
301       */
302      protected void internalAdd(TComplexProperty complexProperty) {
303        this.internalAdd(complexProperty, false);
304      }
305    
306      /**
307       * Add complex property.
308       *
309       * @param complexProperty The complex property.
310       * @param loading         If true, collection is being loaded.
311       */
312      private void internalAdd(TComplexProperty complexProperty,
313          boolean loading) {
314        EwsUtilities.ewsAssert(complexProperty != null, "ComplexPropertyCollection.InternalAdd",
315                               "complexProperty is null");
316    
317        if (!this.items.contains(complexProperty)) {
318          this.items.add(complexProperty);
319          if (!loading) {
320            this.removedItems.remove(complexProperty);
321            this.addedItems.add(complexProperty);
322          }
323          complexProperty.addOnChangeEvent(this);
324          this.changed();
325        }
326      }
327    
328      /**
329       * Complex property changed.
330       *
331       * @param complexProperty accepts ComplexProperty
332       */
333      @Override
334      public void complexPropertyChanged(final TComplexProperty complexProperty) {
335        this.itemChanged(complexProperty);
336      }
337    
338      /**
339       * Clear collection.
340       */
341      protected void internalClear() {
342        while (this.getCount() > 0) {
343          this.internalRemoveAt(0);
344        }
345      }
346    
347      /**
348       * Remote entry at index.
349       *
350       * @param index The index.
351       */
352      protected void internalRemoveAt(int index) {
353        EwsUtilities.ewsAssert(index >= 0 && index < this.getCount(),
354                               "ComplexPropertyCollection.InternalRemoveAt", "index is out of range.");
355    
356        this.internalRemove(this.items.get(index));
357      }
358    
359      /**
360       * Remove specified complex property.
361       *
362       * @param complexProperty The complex property.
363       * @return True if the complex property was successfully removed from the
364       * collection, false otherwise.
365       */
366      protected boolean internalRemove(TComplexProperty complexProperty) {
367        EwsUtilities.ewsAssert(complexProperty != null, "ComplexPropertyCollection.InternalRemove",
368                               "complexProperty is null");
369    
370        if (this.items.remove(complexProperty)) {
371          complexProperty.removeChangeEvent(this);
372          if (!this.addedItems.contains(complexProperty)) {
373            this.removedItems.add(complexProperty);
374          } else {
375            this.addedItems.remove(complexProperty);
376          }
377          this.modifiedItems.remove(complexProperty);
378          this.changed();
379          return true;
380        } else {
381          return false;
382        }
383      }
384    
385      /**
386       * Determines whether a specific property is in the collection.
387       *
388       * @param complexProperty The property to locate in the collection.
389       * @return True if the property was found in the collection, false
390       * otherwise.
391       */
392      public boolean contains(TComplexProperty complexProperty) {
393        return this.items.contains(complexProperty);
394      }
395    
396      /**
397       * Searches for a specific property and return its zero-based index within
398       * the collection.
399       *
400       * @param complexProperty The property to locate in the collection.
401       * @return The zero-based index of the property within the collection.
402       */
403      public int indexOf(TComplexProperty complexProperty) {
404        return this.items.indexOf(complexProperty);
405      }
406    
407      /**
408       * Gets the total number of property in the collection.
409       *
410       * @return the count
411       */
412      public int getCount() {
413        return this.items.size();
414      }
415    
416      /**
417       * Gets the property at the specified index.
418       *
419       * @param index the index
420       * @return index The property at the specified index.
421       * @throws IllegalArgumentException thrown if if index is out of range.
422       */
423      public TComplexProperty getPropertyAtIndex(int index)
424          throws IllegalArgumentException {
425        if (index < 0 || index >= this.getCount()) {
426          throw new IllegalArgumentException(
427              String.format("index %d is out of range [0..%d[.", index, this.getCount())
428          );
429        }
430        return this.items.get(index);
431      }
432    
433      /**
434       * Gets an enumerator that iterates through the elements of the collection.
435       *
436       * @return An Iterator for the collection.
437       */
438      @Override
439      public Iterator<TComplexProperty> iterator() {
440        return this.items.iterator();
441      }
442    
443      /**
444       * Write set update to xml.
445       *
446       * @param writer             accepts EwsServiceXmlWriter
447       * @param ewsObject          accepts ServiceObject
448       * @param propertyDefinition accepts PropertyDefinition
449       * @return true
450       * @throws Exception the exception
451       */
452      @Override
453      public boolean writeSetUpdateToXml(EwsServiceXmlWriter writer,
454          ServiceObject ewsObject, PropertyDefinition propertyDefinition)
455          throws Exception {
456        // If the collection is empty, delete the property.
457        if (this.getCount() == 0) {
458          writer.writeStartElement(XmlNamespace.Types, ewsObject
459              .getDeleteFieldXmlElementName());
460          propertyDefinition.writeToXml(writer);
461          writer.writeEndElement();
462          return true;
463        }
464        // Otherwise, use the default XML serializer.
465        else {
466          return false;
467        }
468      }
469    
470      /**
471       * Writes the deletion update to XML.
472       *
473       * @param writer    The writer.
474       * @param ewsObject The ews object.
475       * @return True if property generated serialization.
476       * @throws Exception the exception
477       */
478      @Override
479      public boolean writeDeleteUpdateToXml(EwsServiceXmlWriter writer,
480          ServiceObject ewsObject) throws Exception {
481        // Use the default XML serializer.
482        return false;
483      }
484    }