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.search.filter;
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.XmlAttributeNames;
030import microsoft.exchange.webservices.data.core.XmlElementNames;
031import microsoft.exchange.webservices.data.core.enumeration.search.ComparisonMode;
032import microsoft.exchange.webservices.data.core.enumeration.search.ContainmentMode;
033import microsoft.exchange.webservices.data.core.enumeration.attribute.EditorBrowsableState;
034import microsoft.exchange.webservices.data.core.enumeration.search.LogicalOperator;
035import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace;
036import microsoft.exchange.webservices.data.core.exception.service.local.ServiceValidationException;
037import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlDeserializationException;
038import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlSerializationException;
039import microsoft.exchange.webservices.data.misc.OutParam;
040import microsoft.exchange.webservices.data.property.complex.ComplexProperty;
041import microsoft.exchange.webservices.data.property.complex.IComplexPropertyChangedDelegate;
042import microsoft.exchange.webservices.data.property.complex.ISearchStringProvider;
043import microsoft.exchange.webservices.data.property.definition.PropertyDefinitionBase;
044import org.apache.commons.logging.Log;
045import org.apache.commons.logging.LogFactory;
046
047import javax.xml.stream.XMLStreamException;
048
049import java.util.ArrayList;
050import java.util.Iterator;
051
052/**
053 * Represents the base search filter class. Use descendant search filter classes
054 * such as SearchFilter.IsEqualTo, SearchFilter.ContainsSubstring and
055 * SearchFilter.SearchFilterCollection to define search filter.
056 */
057public abstract class SearchFilter extends ComplexProperty {
058
059  private static final Log LOG = LogFactory.getLog(SearchFilter.class);
060
061  /**
062   * Initializes a new instance of the SearchFilter class.
063   */
064  protected SearchFilter() {
065  }
066
067  /**
068   * The search.
069   *
070   * @param reader the reader
071   * @return the search filter
072   * @throws Exception the exception
073   */
074  //static SearchFilter search;
075
076  /**
077   * Loads from XML.
078   *
079   * @param reader the reader
080   * @return SearchFilter
081   * @throws Exception the exception
082   */
083  public static SearchFilter loadFromXml(EwsServiceXmlReader reader)
084      throws Exception {
085    reader.ensureCurrentNodeIsStartElement();
086
087    SearchFilter searchFilter = null;
088
089    if (reader.getLocalName().equalsIgnoreCase(XmlElementNames.Exists)) {
090      searchFilter = new Exists();
091    } else if (reader.getLocalName().equalsIgnoreCase(
092        XmlElementNames.Contains)) {
093      searchFilter = new ContainsSubstring();
094    } else if (reader.getLocalName().equalsIgnoreCase(
095        XmlElementNames.Excludes)) {
096      searchFilter = new ExcludesBitmask();
097    } else if (reader.getLocalName().equalsIgnoreCase(XmlElementNames.Not)) {
098      searchFilter = new Not();
099    } else if (reader.getLocalName().equalsIgnoreCase(XmlElementNames.And)) {
100      searchFilter = new SearchFilterCollection(
101          LogicalOperator.And);
102    } else if (reader.getLocalName().equalsIgnoreCase(XmlElementNames.Or)) {
103      searchFilter = new SearchFilterCollection(
104          LogicalOperator.Or);
105    } else if (reader.getLocalName().equalsIgnoreCase(
106        XmlElementNames.IsEqualTo)) {
107      searchFilter = new IsEqualTo();
108    } else if (reader.getLocalName().equalsIgnoreCase(
109        XmlElementNames.IsNotEqualTo)) {
110      searchFilter = new IsNotEqualTo();
111    } else if (reader.getLocalName().equalsIgnoreCase(
112        XmlElementNames.IsGreaterThan)) {
113      searchFilter = new IsGreaterThan();
114    } else if (reader.getLocalName().equalsIgnoreCase(
115        XmlElementNames.IsGreaterThanOrEqualTo)) {
116      searchFilter = new IsGreaterThanOrEqualTo();
117    } else if (reader.getLocalName().equalsIgnoreCase(
118        XmlElementNames.IsLessThan)) {
119      searchFilter = new IsLessThan();
120    } else if (reader.getLocalName().equalsIgnoreCase(
121        XmlElementNames.IsLessThanOrEqualTo)) {
122      searchFilter = new IsLessThanOrEqualTo();
123    } else {
124      searchFilter = null;
125    }
126
127    if (searchFilter != null) {
128      searchFilter.loadFromXml(reader, reader.getLocalName());
129    }
130
131    return searchFilter;
132  }
133
134  /**
135   * Gets the name of the XML element.
136   *
137   * @return the xml element name
138   */
139  protected abstract String getXmlElementName();
140
141  /**
142   * Writes to XML.
143   *
144   * @param writer the writer
145   * @throws Exception the exception
146   */
147  public void writeToXml(EwsServiceXmlWriter writer) throws Exception {
148    super.writeToXml(writer, this.getXmlElementName());
149  }
150
151  /**
152   * Represents a search filter that checks for the presence of a substring
153   * inside a text property. Applications can use ContainsSubstring to define
154   * conditions such as "Field CONTAINS Value" or
155   * "Field IS PREFIXED WITH Value".
156   */
157  public static final class ContainsSubstring extends PropertyBasedFilter {
158
159    /**
160     * The containment mode.
161     */
162    private ContainmentMode containmentMode = ContainmentMode.Substring;
163
164    /**
165     * The comparison mode.
166     */
167    private ComparisonMode comparisonMode = ComparisonMode.IgnoreCase;
168
169    /**
170     * The value.
171     */
172    private String value;
173
174    /**
175     * Initializes a new instance of the class.
176     */
177    public ContainsSubstring() {
178      super();
179    }
180
181    /**
182     * Initializes a new instance of the class.
183     *
184     * @param propertyDefinition The definition of the property that is being compared.
185     * @param value              The value to compare with.
186     */
187    public ContainsSubstring(PropertyDefinitionBase propertyDefinition,
188        String value) {
189      super(propertyDefinition);
190      this.value = value;
191    }
192
193    /**
194     * Initializes a new instance of the class.
195     *
196     * @param propertyDefinition The definition of the property that is being compared.
197     * @param value              The value to compare with.
198     * @param containmentMode    The containment mode.
199     * @param comparisonMode     The comparison mode.
200     */
201    public ContainsSubstring(PropertyDefinitionBase propertyDefinition,
202        String value, ContainmentMode containmentMode,
203        ComparisonMode comparisonMode) {
204      this(propertyDefinition, value);
205      this.containmentMode = containmentMode;
206      this.comparisonMode = comparisonMode;
207    }
208
209    /**
210     * validates instance.
211     *
212     * @throws ServiceValidationException the service validation exception
213     */
214    @Override
215    protected void internalValidate() throws ServiceValidationException {
216      super.internalValidate();
217      if ((this.value == null) || this.value.isEmpty()) {
218        throw new ServiceValidationException("The Value property must be set.");
219      }
220    }
221
222    /**
223     * Gets the name of the XML element.
224     *
225     * @return the xml element name
226     */
227    @Override
228    protected String getXmlElementName() {
229      return XmlElementNames.Contains;
230    }
231
232    /**
233     * Tries to read element from XML.
234     *
235     * @param reader the reader
236     * @return True if element was read.
237     * @throws Exception the exception
238     */
239    @Override
240    public boolean tryReadElementFromXml(EwsServiceXmlReader reader)
241        throws Exception {
242      boolean result = super.tryReadElementFromXml(reader);
243
244      if (!result) {
245        if (reader.getLocalName().equals(XmlElementNames.Constant)) {
246          this.value = reader
247              .readAttributeValue(XmlAttributeNames.Value);
248          result = true;
249        }
250      }
251      return result;
252    }
253
254    /**
255     * Reads the attribute of Xml.
256     *
257     * @param reader the reader
258     * @throws Exception the exception
259     */
260    @Override
261    public void readAttributesFromXml(EwsServiceXmlReader reader)
262        throws Exception {
263
264      super.readAttributesFromXml(reader);
265      this.containmentMode = reader.readAttributeValue(
266          ContainmentMode.class, XmlAttributeNames.ContainmentMode);
267      try {
268        this.comparisonMode = reader.readAttributeValue(
269            ComparisonMode.class,
270            XmlAttributeNames.ContainmentComparison);
271      } catch (IllegalArgumentException ile) {
272        // This will happen if we receive a value that is defined in the
273        // EWS
274        // schema but that is not defined
275        // in the API. We map that
276        // value to IgnoreCaseAndNonSpacingCharacters.
277        this.comparisonMode = ComparisonMode.
278            IgnoreCaseAndNonSpacingCharacters;
279      }
280    }
281
282    /**
283     * Writes the attribute to XML.
284     *
285     * @param writer the writer
286     * @throws ServiceXmlSerializationException the service xml serialization exception
287     */
288    @Override
289    public void writeAttributesToXml(EwsServiceXmlWriter writer)
290        throws ServiceXmlSerializationException {
291      super.writeAttributesToXml(writer);
292
293      writer.writeAttributeValue(XmlAttributeNames.ContainmentMode,
294          this.containmentMode);
295      writer.writeAttributeValue(XmlAttributeNames.ContainmentComparison,
296          this.comparisonMode);
297    }
298
299    /**
300     * Writes the elements to Xml.
301     *
302     * @param writer the writer
303     * @throws XMLStreamException the XML stream exception
304     * @throws ServiceXmlSerializationException the service xml serialization exception
305     */
306    @Override
307    public void writeElementsToXml(EwsServiceXmlWriter writer)
308        throws XMLStreamException, ServiceXmlSerializationException {
309      super.writeElementsToXml(writer);
310
311      writer.writeStartElement(XmlNamespace.Types,
312          XmlElementNames.Constant);
313      writer.writeAttributeValue(XmlAttributeNames.Value, this.value);
314      writer.writeEndElement(); // Constant
315    }
316
317    /**
318     * Gets the containment mode.
319     *
320     * @return ContainmentMode
321     */
322    public ContainmentMode getContainmentMode() {
323      return containmentMode;
324    }
325
326    /**
327     * sets the ContainmentMode.
328     *
329     * @param containmentMode the new containment mode
330     */
331    public void setContainmentMode(ContainmentMode containmentMode) {
332      this.containmentMode = containmentMode;
333    }
334
335    /**
336     * Gets the comparison mode.
337     *
338     * @return ComparisonMode
339     */
340    public ComparisonMode getComparisonMode() {
341      return comparisonMode;
342    }
343
344    /**
345     * sets the comparison mode.
346     *
347     * @param comparisonMode the new comparison mode
348     */
349    public void setComparisonMode(ComparisonMode comparisonMode) {
350      this.comparisonMode = comparisonMode;
351    }
352
353    /**
354     * gets the value to compare the specified property with.
355     *
356     * @return String
357     */
358    public String getValue() {
359      return value;
360    }
361
362    /**
363     * sets the value to compare the specified property with.
364     *
365     * @param value the new value
366     */
367    public void setValue(String value) {
368      this.value = value;
369    }
370  }
371
372
373  /**
374   * Represents a bitmask exclusion search filter. Applications can use
375   * ExcludesBitExcludesBitmaskFilter to define conditions such as
376   * "(OrdinalField and 0x0010) != 0x0010"
377   */
378  public static class ExcludesBitmask extends PropertyBasedFilter {
379
380    /**
381     * The bitmask.
382     */
383    private int bitmask;
384
385    /**
386     * Initializes a new instance of the class.
387     */
388    public ExcludesBitmask() {
389      super();
390    }
391
392    /**
393     * Initializes a new instance of the class.
394     *
395     * @param propertyDefinition the property definition
396     * @param bitmask            the bitmask
397     */
398    public ExcludesBitmask(PropertyDefinitionBase propertyDefinition,
399        int bitmask) {
400      super(propertyDefinition);
401      this.bitmask = bitmask;
402    }
403
404    /**
405     * Gets the name of the XML element.
406     *
407     * @return XML element name
408     */
409    @Override
410    public String getXmlElementName() {
411      return XmlElementNames.Excludes;
412    }
413
414    /**
415     * Tries to read element from XML.
416     *
417     * @param reader the reader
418     * @return true if element was read
419     * @throws Exception the exception
420     */
421    @Override
422    public boolean tryReadElementFromXml(EwsServiceXmlReader reader)
423        throws Exception {
424      boolean result = super.tryReadElementFromXml(reader);
425
426      if (!result) {
427        if (reader.getLocalName().equals(XmlElementNames.Bitmask)) {
428          // EWS always returns the Bitmask value in hexadecimal
429          this.bitmask = Integer.parseInt(reader
430              .readAttributeValue(XmlAttributeNames.Value));
431        }
432      }
433
434      return result;
435    }
436
437    /**
438     * Writes the elements to XML.
439     *
440     * @param writer the writer
441     * @throws javax.xml.stream.XMLStreamException , ServiceXmlSerializationException
442     * @throws ServiceXmlSerializationException    the service xml serialization exception
443     */
444    @Override
445    public void writeElementsToXml(EwsServiceXmlWriter writer)
446        throws XMLStreamException, ServiceXmlSerializationException {
447      super.writeElementsToXml(writer);
448
449      writer.writeStartElement(XmlNamespace.Types,
450          XmlElementNames.Bitmask);
451      writer.writeAttributeValue(XmlAttributeNames.Value, this.bitmask);
452      writer.writeEndElement(); // Bitmask
453    }
454
455    /**
456     * Gets the bitmask to compare the property with.
457     *
458     * @return bitmask
459     */
460    public int getBitmask() {
461      return bitmask;
462    }
463
464    /**
465     * Sets the bitmask to compare the property with.
466     *
467     * @param bitmask the new bitmask
468     */
469    public void setBitmask(int bitmask) {
470      this.bitmask = bitmask;
471    }
472
473  }
474
475
476  /**
477   * Represents a search filter checking if a field is set. Applications can
478   * use ExistsFilter to define conditions such as "Field IS SET".
479   */
480  public static final class Exists extends PropertyBasedFilter {
481
482    /**
483     * Initializes a new instance of the class.
484     */
485    public Exists() {
486      super();
487    }
488
489    /**
490     * Initializes a new instance of the class.
491     *
492     * @param propertyDefinition the property definition
493     */
494    public Exists(PropertyDefinitionBase propertyDefinition) {
495      super(propertyDefinition);
496    }
497
498    /**
499     * Gets the name of the XML element.
500     *
501     * @return the xml element name
502     */
503    @Override
504    protected String getXmlElementName() {
505      return XmlElementNames.Exists;
506    }
507  }
508
509
510  /**
511   * Represents a search filter that checks if a property is equal to a given
512   * value or other property.
513   */
514  public static class IsEqualTo extends RelationalFilter {
515
516    /**
517     * Initializes a new instance of the class.
518     */
519    public IsEqualTo() {
520      super();
521    }
522
523    /**
524     * Initializes a new instance of the class.
525     *
526     * @param propertyDefinition      The definition of the property that is being compared.
527     * @param otherPropertyDefinition The definition of the property to compare with.
528     */
529    public IsEqualTo(PropertyDefinitionBase propertyDefinition,
530        PropertyDefinitionBase otherPropertyDefinition) {
531      super(propertyDefinition, otherPropertyDefinition);
532    }
533
534    /**
535     * Initializes a new instance of the class.
536     *
537     * @param propertyDefinition The definition of the property that is being compared.
538     * @param value              The value of the property to compare with.
539     */
540    public IsEqualTo(PropertyDefinitionBase propertyDefinition,
541        Object value) {
542      super(propertyDefinition, value);
543    }
544
545    /**
546     * Gets the name of the XML element.
547     *
548     * @return the xml element name
549     */
550    @Override
551    protected String getXmlElementName() {
552      return XmlElementNames.IsEqualTo;
553    }
554
555  }
556
557
558  /**
559   * Represents a search filter that checks if a property is greater than a
560   * given value or other property.
561   */
562  public static class IsGreaterThan extends RelationalFilter {
563
564    /**
565     * Initializes a new instance of the class.
566     */
567    public IsGreaterThan() {
568      super();
569    }
570
571    /**
572     * Initializes a new instance of the class.
573     *
574     * @param propertyDefinition      The definition of the property that is being compared.
575     * @param otherPropertyDefinition The definition of the property to compare with.
576     */
577    public IsGreaterThan(PropertyDefinitionBase propertyDefinition,
578        PropertyDefinitionBase otherPropertyDefinition) {
579      super(propertyDefinition, otherPropertyDefinition);
580    }
581
582    /**
583     * Initializes a new instance of the class.
584     *
585     * @param propertyDefinition The definition of the property that is being compared.
586     * @param value              The value of the property to compare with.
587     */
588    public IsGreaterThan(PropertyDefinitionBase propertyDefinition,
589        Object value) {
590      super(propertyDefinition, value);
591    }
592
593    /**
594     * Gets the name of the XML element.
595     *
596     * @return XML element name.
597     */
598    @Override
599    protected String getXmlElementName() {
600      return XmlElementNames.IsGreaterThan;
601    }
602  }
603
604
605  /**
606   * Represents a search filter that checks if a property is greater than or
607   * equal to a given value or other property.
608   */
609  public static class IsGreaterThanOrEqualTo extends RelationalFilter {
610
611    /**
612     * Initializes a new instance of the class.
613     */
614    public IsGreaterThanOrEqualTo() {
615      super();
616    }
617
618    /**
619     * Initializes a new instance of the class.
620     *
621     * @param propertyDefinition      The definition of the property that is being compared.
622     * @param otherPropertyDefinition The definition of the property to compare with.
623     */
624    public IsGreaterThanOrEqualTo(
625        PropertyDefinitionBase propertyDefinition,
626        PropertyDefinitionBase otherPropertyDefinition) {
627      super(propertyDefinition, otherPropertyDefinition);
628    }
629
630    /**
631     * Initializes a new instance of the class.
632     *
633     * @param propertyDefinition The definition of the property that is being compared.
634     * @param value              The value of the property to compare with.
635     */
636    public IsGreaterThanOrEqualTo(
637        PropertyDefinitionBase propertyDefinition, Object value) {
638      super(propertyDefinition, value);
639    }
640
641    /**
642     * Gets the name of the XML element. XML element name.
643     *
644     * @return the xml element name
645     */
646    @Override
647    protected String getXmlElementName() {
648      return XmlElementNames.IsGreaterThanOrEqualTo;
649    }
650
651  }
652
653
654  /**
655   * Represents a search filter that checks if a property is less than a given
656   * value or other property.
657   */
658  public static class IsLessThan extends RelationalFilter {
659
660    /**
661     * Initializes a new instance of the class.
662     */
663    public IsLessThan() {
664      super();
665    }
666
667    /**
668     * Initializes a new instance of the class.
669     *
670     * @param propertyDefinition      The definition of the property that is being compared.
671     * @param otherPropertyDefinition The definition of the property to compare with.
672     */
673    public IsLessThan(PropertyDefinitionBase propertyDefinition,
674        PropertyDefinitionBase otherPropertyDefinition) {
675      super(propertyDefinition, otherPropertyDefinition);
676    }
677
678    /**
679     * Initializes a new instance of the class.
680     *
681     * @param propertyDefinition The definition of the property that is being compared.
682     * @param value              The value of the property to compare with.
683     */
684    public IsLessThan(PropertyDefinitionBase propertyDefinition,
685        Object value) {
686      super(propertyDefinition, value);
687    }
688
689    /**
690     * Gets the name of the XML element. XML element name.
691     *
692     * @return the xml element name
693     */
694    @Override
695    protected String getXmlElementName() {
696      return XmlElementNames.IsLessThan;
697    }
698
699  }
700
701
702  /**
703   * Represents a search filter that checks if a property is less than or
704   * equal to a given value or other property.
705   */
706  public static class IsLessThanOrEqualTo extends RelationalFilter {
707
708    /**
709     * Initializes a new instance of the class.
710     */
711    public IsLessThanOrEqualTo() {
712      super();
713    }
714
715    /**
716     * Initializes a new instance of the class.
717     *
718     * @param propertyDefinition      The definition of the property that is being compared.
719     * @param otherPropertyDefinition The definition of the property to compare with.
720     */
721    public IsLessThanOrEqualTo(PropertyDefinitionBase propertyDefinition,
722        PropertyDefinitionBase otherPropertyDefinition) {
723      super(propertyDefinition, otherPropertyDefinition);
724    }
725
726    /**
727     * Initializes a new instance of the class.
728     *
729     * @param propertyDefinition The definition of the property that is being compared.
730     * @param value              The value of the property to compare with.
731     */
732    public IsLessThanOrEqualTo(PropertyDefinitionBase propertyDefinition,
733        Object value) {
734      super(propertyDefinition, value);
735    }
736
737    /**
738     * Gets the name of the XML element. XML element name.
739     *
740     * @return the xml element name
741     */
742    @Override
743    protected String getXmlElementName() {
744      return XmlElementNames.IsLessThanOrEqualTo;
745    }
746
747  }
748
749
750  /**
751   * Represents a search filter that checks if a property is not equal to a
752   * given value or other property.
753   */
754  public static class IsNotEqualTo extends RelationalFilter {
755
756    /**
757     * Initializes a new instance of the class.
758     */
759    public IsNotEqualTo() {
760      super();
761    }
762
763    /**
764     * Initializes a new instance of the class.
765     *
766     * @param propertyDefinition      The definition of the property that is being compared.
767     * @param otherPropertyDefinition The definition of the property to compare with.
768     */
769    public IsNotEqualTo(PropertyDefinitionBase propertyDefinition,
770        PropertyDefinitionBase otherPropertyDefinition) {
771      super(propertyDefinition, otherPropertyDefinition);
772    }
773
774    /**
775     * Initializes a new instance of the class.
776     *
777     * @param propertyDefinition The definition of the property that is being compared.
778     * @param value              The value of the property to compare with.
779     */
780    public IsNotEqualTo(PropertyDefinitionBase propertyDefinition,
781        Object value) {
782      super(propertyDefinition, value);
783    }
784
785    /**
786     * Gets the name of the XML element.
787     *
788     * @return XML element name.
789     */
790    @Override
791    protected String getXmlElementName() {
792      return XmlElementNames.IsNotEqualTo;
793    }
794
795  }
796
797
798  /**
799   * Represents a search filter that negates another. Applications can use
800   * NotFilter to define conditions such as "NOT(other filter)".
801   */
802  public static class Not extends SearchFilter implements IComplexPropertyChangedDelegate {
803
804    /**
805     * The search filter.
806     */
807    private SearchFilter searchFilter;
808
809    /**
810     * Initializes a new instance of the class.
811     */
812    public Not() {
813      super();
814    }
815
816    /**
817     * Initializes a new instance of the class.
818     *
819     * @param searchFilter the search filter
820     */
821    public Not(SearchFilter searchFilter) {
822      super();
823      this.searchFilter = searchFilter;
824    }
825
826    /**
827     * Search filter changed.
828     *
829     * @param complexProperty the complex property
830     */
831    private void searchFilterChanged(ComplexProperty complexProperty) {
832      this.changed();
833    }
834
835    /**
836     * validates the instance.
837     *
838     * @throws ServiceValidationException the service validation exception
839     */
840    @Override
841    protected void internalValidate() throws ServiceValidationException {
842      if (this.searchFilter == null) {
843        throw new ServiceValidationException("The SearchFilter property must be set.");
844      }
845    }
846
847    /**
848     * Gets the name of the XML element.
849     *
850     * @return the xml element name
851     */
852    @Override
853    protected String getXmlElementName() {
854      return XmlElementNames.Not;
855    }
856
857    /**
858     * Tries to read element from XML.
859     *
860     * @param reader the reader
861     * @return true if the element was read
862     * @throws Exception the exception
863     */
864    @Override
865    public boolean tryReadElementFromXml(EwsServiceXmlReader reader)
866        throws Exception {
867      this.searchFilter = SearchFilter.loadFromXml(reader);
868      return true;
869    }
870
871    /**
872     * Writes the elements to XML.
873     *
874     * @param writer the writer
875     * @throws Exception the exception
876     */
877    @Override
878    public void writeElementsToXml(EwsServiceXmlWriter writer)
879        throws Exception {
880      this.searchFilter.writeToXml(writer);
881    }
882
883    /**
884     * Gets  the search filter to negate. Available search filter
885     * classes include SearchFilter.IsEqualTo,
886     * SearchFilter.ContainsSubstring and
887     * SearchFilter.SearchFilterCollection.
888     *
889     * @return SearchFilter
890     */
891    public SearchFilter getSearchFilter() {
892      return searchFilter;
893    }
894
895    /**
896     * Sets the search filter to negate. Available search filter classes
897     * include SearchFilter.IsEqualTo, SearchFilter.ContainsSubstring and
898     * SearchFilter.SearchFilterCollection.
899     *
900     * @param searchFilter the new search filter
901     */
902    public void setSearchFilter(SearchFilter searchFilter) {
903      if (this.searchFilter != null) {
904        this.searchFilter.removeChangeEvent(this);
905      }
906
907      if (this.canSetFieldValue(this.searchFilter, searchFilter)) {
908        this.searchFilter = searchFilter;
909        this.changed();
910
911      }
912
913      if (this.searchFilter != null) {
914        this.searchFilter.addOnChangeEvent(this);
915      }
916    }
917
918    /*
919     * (non-Javadoc)
920     *
921     * @see
922     * microsoft.exchange.webservices.
923     * ComplexPropertyChangedDelegateInterface#
924     * complexPropertyChanged(microsoft.exchange.webservices.ComplexProperty
925     * )
926     */
927    @Override
928    public void complexPropertyChanged(ComplexProperty complexProperty) {
929      searchFilterChanged(complexProperty);
930
931    }
932  }
933
934
935  /**
936   * Represents a search filter where an item or folder property is involved.
937   */
938  @EditorBrowsable(state = EditorBrowsableState.Never)
939  public static abstract class PropertyBasedFilter extends SearchFilter {
940
941    /**
942     * The property definition.
943     */
944    private PropertyDefinitionBase propertyDefinition;
945
946    /**
947     * Initializes a new instance of the class.
948     */
949    PropertyBasedFilter() {
950      super();
951    }
952
953    /**
954     * Initializes a new instance of the class.
955     *
956     * @param propertyDefinition the property definition
957     */
958    PropertyBasedFilter(PropertyDefinitionBase propertyDefinition) {
959      super();
960      this.propertyDefinition = propertyDefinition;
961    }
962
963    /**
964     * validate instance.
965     *
966     * @throws ServiceValidationException the service validation exception
967     */
968    @Override
969    protected void internalValidate() throws ServiceValidationException {
970      if (this.propertyDefinition == null) {
971        throw new ServiceValidationException("The PropertyDefinition property must be set.");
972      }
973    }
974
975    /**
976     * Tries to read element from XML.
977     *
978     * @param reader the reader
979     * @return true if element was read
980     * @throws Exception the exception
981     */
982    @Override
983    public boolean tryReadElementFromXml(EwsServiceXmlReader reader)
984        throws Exception {
985      OutParam<PropertyDefinitionBase> outParam =
986          new OutParam<PropertyDefinitionBase>();
987      outParam.setParam(this.propertyDefinition);
988
989      return PropertyDefinitionBase.tryLoadFromXml(reader, outParam);
990    }
991
992    /**
993     * Writes the elements to XML.
994     *
995     * @param writer the writer
996     * @throws XMLStreamException the XML stream exception
997     * @throws ServiceXmlSerializationException the service xml serialization exception
998     */
999    @Override
1000    public void writeElementsToXml(EwsServiceXmlWriter writer)
1001        throws XMLStreamException, ServiceXmlSerializationException {
1002      this.propertyDefinition.writeToXml(writer);
1003    }
1004
1005    /**
1006     * Gets the definition of the property that is involved in the search
1007     * filter.
1008     *
1009     * @return propertyDefinition
1010     */
1011    public PropertyDefinitionBase getPropertyDefinition() {
1012      return this.propertyDefinition;
1013    }
1014
1015    /**
1016     * Sets the definition of the property that is involved in the search
1017     * filter.
1018     *
1019     * @param propertyDefinition the new property definition
1020     */
1021    public void setPropertyDefinition(
1022        PropertyDefinitionBase propertyDefinition) {
1023      this.propertyDefinition = propertyDefinition;
1024    }
1025  }
1026
1027
1028  /**
1029   * Represents the base class for relational filter (for example, IsEqualTo,
1030   * IsGreaterThan or IsLessThanOrEqualTo).
1031   */
1032  @EditorBrowsable(state = EditorBrowsableState.Never)
1033  public abstract static class RelationalFilter extends PropertyBasedFilter {
1034
1035    /**
1036     * The other property definition.
1037     */
1038    private PropertyDefinitionBase otherPropertyDefinition;
1039
1040    /**
1041     * The value.
1042     */
1043    private Object value;
1044
1045    /**
1046     * Initializes a new instance of the class.
1047     */
1048    RelationalFilter() {
1049      super();
1050    }
1051
1052    /**
1053     * Initializes a new instance of the class.
1054     *
1055     * @param propertyDefinition      The definition of the property that is being compared.
1056     * @param otherPropertyDefinition The definition of the property to compare with
1057     */
1058    RelationalFilter(PropertyDefinitionBase propertyDefinition,
1059        PropertyDefinitionBase otherPropertyDefinition) {
1060      super(propertyDefinition);
1061      this.otherPropertyDefinition = otherPropertyDefinition;
1062    }
1063
1064    /**
1065     * Initializes a new instance of the class.
1066     *
1067     * @param propertyDefinition The definition of the property that is being compared.
1068     * @param value              The value to compare with.
1069     */
1070    RelationalFilter(PropertyDefinitionBase propertyDefinition,
1071        Object value) {
1072      super(propertyDefinition);
1073      this.value = value;
1074    }
1075
1076    /**
1077     * validates the instance.
1078     *
1079     * @throws ServiceValidationException the service validation exception
1080     */
1081    @Override
1082    protected void internalValidate() throws ServiceValidationException {
1083      super.internalValidate();
1084
1085      if (this.otherPropertyDefinition == null && this.value == null) {
1086        throw new ServiceValidationException(
1087            "Either the OtherPropertyDefinition or the Value property must be set.");
1088      }
1089    }
1090
1091    /**
1092     * Tries to read element from XML.
1093     *
1094     * @param reader the reader
1095     * @return true if element was read
1096     * @throws Exception the exception
1097     */
1098    @Override
1099    public boolean tryReadElementFromXml(EwsServiceXmlReader reader)
1100        throws Exception {
1101      boolean result = super.tryReadElementFromXml(reader);
1102
1103      if (!result) {
1104        if (reader.getLocalName().equals(
1105            XmlElementNames.FieldURIOrConstant)) {
1106          try {
1107            reader.read();
1108            reader.ensureCurrentNodeIsStartElement();
1109          } catch (ServiceXmlDeserializationException e) {
1110            LOG.error(e);
1111          } catch (XMLStreamException e) {
1112            LOG.error(e);
1113          }
1114
1115          if (reader.isStartElement(XmlNamespace.Types,
1116              XmlElementNames.Constant)) {
1117            this.value = reader
1118                .readAttributeValue(XmlAttributeNames.Value);
1119            result = true;
1120          } else {
1121            OutParam<PropertyDefinitionBase> outParam =
1122                new OutParam<PropertyDefinitionBase>();
1123            outParam.setParam(this.otherPropertyDefinition);
1124
1125            result = PropertyDefinitionBase.tryLoadFromXml(reader,
1126                outParam);
1127          }
1128        }
1129      }
1130
1131      return result;
1132    }
1133
1134    /**
1135     * Writes the elements to XML.
1136     *
1137     * @param writer the writer
1138     * @throws javax.xml.stream.XMLStreamException , ServiceXmlSerializationException
1139     * @throws ServiceXmlSerializationException    the service xml serialization exception
1140     */
1141    @Override
1142    public void writeElementsToXml(EwsServiceXmlWriter writer)
1143        throws XMLStreamException, ServiceXmlSerializationException {
1144      super.writeElementsToXml(writer);
1145
1146      writer.writeStartElement(XmlNamespace.Types,
1147          XmlElementNames.FieldURIOrConstant);
1148
1149      if (this.value != null) {
1150        writer.writeStartElement(XmlNamespace.Types,
1151            XmlElementNames.Constant);
1152        writer.writeAttributeValue(XmlAttributeNames.Value,
1153            true /* alwaysWriteEmptyString */, this.value);
1154        writer.writeEndElement(); // Constant
1155      } else {
1156        this.otherPropertyDefinition.writeToXml(writer);
1157      }
1158
1159      writer.writeEndElement(); // FieldURIOrConstant
1160    }
1161
1162    /**
1163     * Gets the definition of the property to compare with.
1164     *
1165     * @return otherPropertyDefinition
1166     */
1167    public PropertyDefinitionBase getOtherPropertyDefinition() {
1168      return this.otherPropertyDefinition;
1169    }
1170
1171    /**
1172     * Sets the definition of the property to compare with.
1173     *
1174     * @param OtherPropertyDefinition the new other property definition
1175     */
1176    public void setOtherPropertyDefinition(
1177        PropertyDefinitionBase OtherPropertyDefinition) {
1178      this.otherPropertyDefinition = OtherPropertyDefinition;
1179      this.value = null;
1180    }
1181
1182    /**
1183     * Gets the value of the property to compare with.
1184     *
1185     * @return the value
1186     */
1187    public Object getValue() {
1188      return value;
1189    }
1190
1191    /**
1192     * Sets the value of the property to compare with.
1193     *
1194     * @param value the new value
1195     */
1196    public void setValue(Object value) {
1197      this.value = value;
1198      this.otherPropertyDefinition = null;
1199    }
1200
1201    /**
1202     * gets Xml Element name.
1203     *
1204     * @return the xml element name
1205     */
1206    @Override
1207    protected String getXmlElementName() {
1208      return null;
1209    }
1210  }
1211
1212
1213  /**
1214   * Represents a collection of search filter linked by a logical operator.
1215   * Applications can use SearchFilterCollection to define complex search
1216   * filter such as "Condition1 AND Condition2".
1217   */
1218  public static class SearchFilterCollection extends SearchFilter implements
1219      Iterable<SearchFilter>, IComplexPropertyChangedDelegate {
1220
1221    /**
1222     * The logical operator.
1223     */
1224    private LogicalOperator logicalOperator = LogicalOperator.And;
1225
1226    /**
1227     * The search filter.
1228     */
1229    private ArrayList<SearchFilter> searchFilters =
1230        new ArrayList<SearchFilter>();
1231
1232    /**
1233     * Initializes a new instance of the class.
1234     */
1235    public SearchFilterCollection() {
1236      super();
1237    }
1238
1239    /**
1240     * Initializes a new instance of the class.
1241     *
1242     * @param logicalOperator The logical operator used to initialize the collection.
1243     */
1244    public SearchFilterCollection(LogicalOperator logicalOperator) {
1245      this.logicalOperator = logicalOperator;
1246    }
1247
1248    /**
1249     * Initializes a new instance of the class.
1250     *
1251     * @param logicalOperator The logical operator used to initialize the collection.
1252     * @param searchFilters   The search filter to add to the collection.
1253     */
1254    public SearchFilterCollection(LogicalOperator logicalOperator,
1255        SearchFilter... searchFilters) {
1256      this(logicalOperator);
1257      for (SearchFilter search : searchFilters) {
1258        Iterable<SearchFilter> searchFil = java.util.Arrays
1259            .asList(search);
1260        this.addRange(searchFil);
1261      }
1262    }
1263
1264    /**
1265     * Initializes a new instance of the class.
1266     *
1267     * @param logicalOperator The logical operator used to initialize the collection.
1268     * @param searchFilters   The search filter to add to the collection.
1269     */
1270    public SearchFilterCollection(LogicalOperator logicalOperator,
1271        Iterable<SearchFilter> searchFilters) {
1272      this(logicalOperator);
1273      this.addRange(searchFilters);
1274    }
1275
1276    /**
1277     * Validate instance.
1278     *
1279     * @throws Exception
1280     */
1281    @Override
1282    protected void internalValidate() throws Exception {
1283      for (int i = 0; i < this.getCount(); i++) {
1284        try {
1285          this.searchFilters.get(i).internalValidate();
1286        } catch (ServiceValidationException e) {
1287          throw new ServiceValidationException(String.format("The search filter at index %d is invalid.", i),
1288              e);
1289        }
1290      }
1291    }
1292
1293    /**
1294     * A search filter has changed.
1295     *
1296     * @param complexProperty The complex property
1297     */
1298    private void searchFilterChanged(ComplexProperty complexProperty) {
1299      this.changed();
1300    }
1301
1302    /**
1303     * Gets the name of the XML element.
1304     *
1305     * @return xml element name
1306     */
1307    @Override
1308    protected String getXmlElementName() {
1309      return this.logicalOperator.toString();
1310    }
1311
1312    /**
1313     * Tries to read element from XML.
1314     *
1315     * @param reader the reader
1316     * @return true, if successful
1317     * @throws Exception the exception
1318     */
1319    @Override
1320    public boolean tryReadElementFromXml(EwsServiceXmlReader reader)
1321        throws Exception {
1322
1323      this.add(SearchFilter.loadFromXml(reader));
1324      return true;
1325    }
1326
1327    /**
1328     * Writes the elements to XML.
1329     *
1330     * @param writer the writer
1331     * @throws Exception the exception
1332     */
1333    @Override
1334    public void writeElementsToXml(EwsServiceXmlWriter writer)
1335        throws Exception {
1336      for (SearchFilter searchFilter : this.searchFilters) {
1337        searchFilter.writeToXml(writer);
1338      }
1339    }
1340
1341    /**
1342     * Writes to XML.
1343     *
1344     * @param writer the writer
1345     * @throws Exception the exception
1346     */
1347    @Override public void writeToXml(EwsServiceXmlWriter writer) throws Exception {
1348      // If there is only one filter in the collection, which developers
1349      // tend
1350      // to do,
1351      // we need to not emit the collection and instead only emit the one
1352      // filter within
1353      // the collection. This is to work around the fact that EWS does not
1354      // allow filter
1355      // collections that have less than two elements.
1356      if (this.getCount() == 1) {
1357        this.searchFilters.get(0).writeToXml(writer);
1358      } else {
1359        super.writeToXml(writer);
1360      }
1361    }
1362
1363    /**
1364     * Adds a search filter of any type to the collection.
1365     *
1366     * @param searchFilter >The search filter to add. Available search filter classes
1367     *                     include SearchFilter.IsEqualTo,
1368     *                     SearchFilter.ContainsSubstring and
1369     *                     SearchFilter.SearchFilterCollection.
1370     */
1371    public void add(SearchFilter searchFilter) {
1372      if (searchFilter == null) {
1373        throw new IllegalArgumentException("searchFilter");
1374      }
1375      searchFilter.addOnChangeEvent(this);
1376      this.searchFilters.add(searchFilter);
1377      this.changed();
1378    }
1379
1380    /**
1381     * Adds multiple search filter to the collection.
1382     *
1383     * @param searchFilters The search filter to add. Available search filter classes
1384     *                      include SearchFilter.IsEqualTo,
1385     *                      SearchFilter.ContainsSubstring and
1386     *                      SearchFilter.SearchFilterCollection
1387     */
1388    public void addRange(Iterable<SearchFilter> searchFilters) {
1389      if (searchFilters == null) {
1390        throw new IllegalArgumentException("searchFilters");
1391      }
1392
1393      for (SearchFilter searchFilter : searchFilters) {
1394        searchFilter.addOnChangeEvent(this);
1395        this.searchFilters.add(searchFilter);
1396      }
1397      this.changed();
1398    }
1399
1400    /**
1401     * Clears the collection.
1402     */
1403    public void clear() {
1404      if (this.getCount() > 0) {
1405        for (SearchFilter searchFilter : this.searchFilters) {
1406          searchFilter.removeChangeEvent(this);
1407        }
1408        this.searchFilters.clear();
1409        this.changed();
1410      }
1411    }
1412
1413    /**
1414     * Determines whether a specific search filter is in the collection.
1415     *
1416     * @param searchFilter The search filter to locate in the collection.
1417     * @return True is the search filter was found in the collection, false
1418     * otherwise.
1419     */
1420    public boolean contains(SearchFilter searchFilter) {
1421      return this.searchFilters.contains(searchFilter);
1422    }
1423
1424    /**
1425     * Removes a search filter from the collection.
1426     *
1427     * @param searchFilter The search filter to remove
1428     */
1429    public void remove(SearchFilter searchFilter) {
1430      if (searchFilter == null) {
1431        throw new IllegalArgumentException("searchFilter");
1432      }
1433
1434      if (this.contains(searchFilter)) {
1435        searchFilter.removeChangeEvent(this);
1436        this.searchFilters.remove(searchFilter);
1437        this.changed();
1438      }
1439    }
1440
1441    /**
1442     * Removes the search filter at the specified index from the collection.
1443     *
1444     * @param index The zero-based index of the search filter to remove.
1445     */
1446    public void removeAt(int index) {
1447      if (index < 0 || index >= this.getCount()) {
1448        throw new IllegalArgumentException(
1449            String.format("index %d is out of range [0..%d[.", index, this.getCount()));
1450      }
1451
1452      this.searchFilters.get(index).removeChangeEvent(this);
1453      this.searchFilters.remove(index);
1454      this.changed();
1455    }
1456
1457    /**
1458     * Gets the total number of search filter in the collection.
1459     *
1460     * @return the count
1461     */
1462    public int getCount() {
1463
1464      return this.searchFilters.size();
1465    }
1466
1467    /**
1468     * Gets the search filter at the specified index.
1469     *
1470     * @param index the index
1471     * @return The search filter at the specified index.
1472     */
1473    public SearchFilter getSearchFilter(int index) {
1474      if (index < 0 || index >= this.getCount()) {
1475        throw new IllegalArgumentException(
1476            String.format("index %d is out of range [0..%d[.", index, this.getCount())
1477        );
1478      }
1479      return this.searchFilters.get(index);
1480    }
1481
1482    /**
1483     * Sets the search filter at the specified index.
1484     *
1485     * @param index        the index
1486     * @param searchFilter the search filter
1487     */
1488    public void setSearchFilter(int index, SearchFilter searchFilter) {
1489      if (index < 0 || index >= this.getCount()) {
1490        throw new IllegalArgumentException(
1491            String.format("index %d is out of range [0..%d[.", index, this.getCount())
1492        );
1493      }
1494      this.searchFilters.add(index, searchFilter);
1495    }
1496
1497    /**
1498     * Gets the logical operator that links the serach filter in this
1499     * collection.
1500     *
1501     * @return LogicalOperator
1502     */
1503    public LogicalOperator getLogicalOperator() {
1504      return logicalOperator;
1505    }
1506
1507    /**
1508     * Sets the logical operator that links the serach filter in this
1509     * collection.
1510     *
1511     * @param logicalOperator the new logical operator
1512     */
1513    public void setLogicalOperator(LogicalOperator logicalOperator) {
1514      this.logicalOperator = logicalOperator;
1515    }
1516
1517    /*
1518     * (non-Javadoc)
1519     *
1520     * @see
1521     * microsoft.exchange.webservices.
1522     * ComplexPropertyChangedDelegateInterface#
1523     * complexPropertyChanged(microsoft.exchange.webservices.ComplexProperty
1524     * )
1525     */
1526    @Override
1527    public void complexPropertyChanged(ComplexProperty complexProperty) {
1528      searchFilterChanged(complexProperty);
1529    }
1530
1531    /*
1532     * (non-Javadoc)
1533     *
1534     * @see java.lang.Iterable#iterator()
1535     */
1536    @Override
1537    public Iterator<SearchFilter> iterator() {
1538      return this.searchFilters.iterator();
1539    }
1540
1541  }
1542}