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.time;
025
026import microsoft.exchange.webservices.data.core.EwsServiceXmlReader;
027import microsoft.exchange.webservices.data.core.EwsServiceXmlWriter;
028import microsoft.exchange.webservices.data.core.XmlAttributeNames;
029import microsoft.exchange.webservices.data.core.XmlElementNames;
030import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion;
031import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace;
032import microsoft.exchange.webservices.data.core.enumeration.property.time.DayOfTheWeek;
033import microsoft.exchange.webservices.data.core.exception.service.local.InvalidOrUnsupportedTimeZoneDefinitionException;
034import microsoft.exchange.webservices.data.core.exception.service.local.ServiceLocalException;
035import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlSerializationException;
036import microsoft.exchange.webservices.data.property.complex.ComplexProperty;
037
038import java.util.ArrayList;
039import java.util.Collections;
040import java.util.Comparator;
041import java.util.Date;
042import java.util.HashMap;
043import java.util.Iterator;
044import java.util.List;
045import java.util.Map;
046
047/**
048 * Represents a time zone as defined by the EWS schema.
049 */
050public class TimeZoneDefinition extends ComplexProperty implements Comparator<TimeZoneTransition> {
051
052  /**
053   * Prefix for generated ids.
054   */
055  private static String NoIdPrefix = "NoId_";
056
057  /**
058   * The Standard period id.
059   */
060  protected final String StandardPeriodId = "Std";
061
062  /**
063   * The Standard period name.
064   */
065  protected final String StandardPeriodName = "Standard";
066
067  /**
068   * The Daylight period id.
069   */
070  protected final String DaylightPeriodId = "Dlt";
071
072  /**
073   * The Daylight period name.
074   */
075  protected final String DaylightPeriodName = "Daylight";
076
077  /**
078   * The name.
079   */
080  public String name;
081
082  /**
083   * The id.
084   */
085  public String id;
086
087  /**
088   * The periods.
089   */
090  private Map<String, TimeZonePeriod> periods =
091      new HashMap<String, TimeZonePeriod>();
092
093  /**
094   * The transition groups.
095   */
096  private Map<String, TimeZoneTransitionGroup> transitionGroups =
097      new HashMap<String, TimeZoneTransitionGroup>();
098
099  /**
100   * The transitions.
101   */
102  private List<TimeZoneTransition> transitions =
103      new ArrayList<TimeZoneTransition>();
104
105  /**
106   * Compares the transitions.
107   *
108   * @param x The first transition.
109   * @param y The second transition.
110   * @return A negative number if x is less than y, 0 if x and y are equal, a
111   * positive number if x is greater than y.
112   */
113  @Override
114  public int compare(final TimeZoneTransition x, final TimeZoneTransition y) {
115    if (x == y) {
116      return 0;
117    } else if (x != null && y != null) {
118      if (x instanceof AbsoluteDateTransition && y instanceof AbsoluteDateTransition) {
119        final AbsoluteDateTransition firstTransition = (AbsoluteDateTransition) x;
120        final AbsoluteDateTransition secondTransition = (AbsoluteDateTransition) y;
121
122        final Date firstDateTime = firstTransition.getDateTime();
123        final Date secondDateTime = secondTransition.getDateTime();
124
125        return firstDateTime.compareTo(secondDateTime);
126
127      } else if (y instanceof TimeZoneTransition) {
128        return 1;
129      }
130    } else if (y == null) {
131      return 1;
132    }
133    return -1;
134  }
135
136  /**
137   * Initializes a new instance of the TimeZoneDefinition class.
138   */
139  public TimeZoneDefinition() {
140    super();
141  }
142
143
144  /**
145   * Adds a transition group with a single transition to the specified period.
146   *
147   * @param timeZonePeriod the time zone period
148   * @return A TimeZoneTransitionGroup.
149   */
150  private TimeZoneTransitionGroup createTransitionGroupToPeriod(
151      TimeZonePeriod timeZonePeriod) {
152    TimeZoneTransition transitionToPeriod = new TimeZoneTransition(this,
153        timeZonePeriod);
154
155    TimeZoneTransitionGroup transitionGroup = new TimeZoneTransitionGroup(
156        this, String.valueOf(this.transitionGroups.size()));
157    transitionGroup.getTransitions().add(transitionToPeriod);
158    this.transitionGroups.put(transitionGroup.getId(), transitionGroup);
159    return transitionGroup;
160  }
161
162  /**
163   * Reads the attribute from XML.
164   *
165   * @param reader the reader
166   * @throws Exception the exception
167   */
168  @Override
169  public void readAttributesFromXml(EwsServiceXmlReader reader)
170      throws Exception {
171    this.name = reader.readAttributeValue(XmlAttributeNames.Name);
172    this.id = reader.readAttributeValue(XmlAttributeNames.Id);
173
174    // E14:319057 -- EWS can return a TimeZone definition with no Id. Generate a new Id in this case.
175    if (this.id == null || this.id.isEmpty()) {
176      String nameValue = (this.getName() == null || this.
177          getName().isEmpty()) ? "" : this.getName();
178      this.setId(NoIdPrefix + Math.abs(nameValue.hashCode()));
179    }
180  }
181
182  /**
183   * Writes the attribute to XML.
184   *
185   * @param writer the writer
186   * @throws ServiceXmlSerializationException the service xml serialization exception
187   */
188  @Override
189  public void writeAttributesToXml(EwsServiceXmlWriter writer)
190      throws ServiceXmlSerializationException {
191    // The Name attribute is only supported in Exchange 2010 and above.
192    if (writer.getService().getRequestedServerVersion() != ExchangeVersion.Exchange2007_SP1) {
193      writer.writeAttributeValue(XmlAttributeNames.Name, this.name);
194    }
195
196    writer.writeAttributeValue(XmlAttributeNames.Id, this.id);
197  }
198
199  /**
200   * Tries to read element from XML.
201   *
202   * @param reader the reader
203   * @return True if element was read.
204   * @throws Exception the exception
205   */
206  @Override
207  public boolean tryReadElementFromXml(EwsServiceXmlReader reader)
208      throws Exception {
209    if (reader.getLocalName().equals(XmlElementNames.Periods)) {
210      do {
211        reader.read();
212        if (reader.isStartElement(XmlNamespace.Types,
213            XmlElementNames.Period)) {
214          TimeZonePeriod period = new TimeZonePeriod();
215          period.loadFromXml(reader);
216
217          this.periods.put(period.getId(), period);
218        }
219      } while (!reader.isEndElement(XmlNamespace.Types,
220          XmlElementNames.Periods));
221
222      return true;
223    } else if (reader.getLocalName().equals(
224        XmlElementNames.TransitionsGroups)) {
225      do {
226        reader.read();
227        if (reader.isStartElement(XmlNamespace.Types,
228            XmlElementNames.TransitionsGroup)) {
229          TimeZoneTransitionGroup transitionGroup =
230              new TimeZoneTransitionGroup(
231                  this);
232
233          transitionGroup.loadFromXml(reader);
234
235          this.transitionGroups.put(transitionGroup.getId(),
236              transitionGroup);
237        }
238      } while (!reader.isEndElement(XmlNamespace.Types,
239          XmlElementNames.TransitionsGroups));
240
241      return true;
242    } else if (reader.getLocalName().equals(XmlElementNames.Transitions)) {
243      do {
244        reader.read();
245        if (reader.isStartElement()) {
246          TimeZoneTransition transition = TimeZoneTransition.create(
247              this, reader.getLocalName());
248
249          transition.loadFromXml(reader);
250
251          this.transitions.add(transition);
252        }
253      } while (!reader.isEndElement(XmlNamespace.Types,
254          XmlElementNames.Transitions));
255
256      return true;
257    } else {
258      return false;
259    }
260  }
261
262  /**
263   * Loads from XML.
264   *
265   * @param reader the reader
266   * @throws Exception the exception
267   */
268  public void loadFromXml(EwsServiceXmlReader reader) throws Exception {
269    this.loadFromXml(reader, XmlElementNames.TimeZoneDefinition);
270    Collections.sort(this.transitions, new TimeZoneDefinition());
271  }
272
273  /**
274   * Writes elements to XML.
275   *
276   * @param writer the writer
277   * @throws Exception the exception
278   */
279  @Override
280  public void writeElementsToXml(EwsServiceXmlWriter writer)
281      throws Exception {
282    // We only emit the full time zone definition against Exchange 2010
283    // servers and above.
284    if (writer.getService().getRequestedServerVersion() != ExchangeVersion.Exchange2007_SP1) {
285      if (this.periods.size() > 0) {
286        writer.writeStartElement(XmlNamespace.Types,
287            XmlElementNames.Periods);
288
289        Iterator<TimeZonePeriod> it = this.periods.values().iterator();
290        while (it.hasNext()) {
291          it.next().writeToXml(writer);
292        }
293
294        writer.writeEndElement(); // Periods
295      }
296
297      if (this.transitionGroups.size() > 0) {
298        writer.writeStartElement(XmlNamespace.Types,
299            XmlElementNames.TransitionsGroups);
300        for (int i = 0; i < this.transitionGroups.size(); i++) {
301          Object key[] = this.transitionGroups.keySet().toArray();
302          this.transitionGroups.get(key[i]).writeToXml(writer);
303        }
304        writer.writeEndElement(); // TransitionGroups
305      }
306
307      if (this.transitions.size() > 0) {
308        writer.writeStartElement(XmlNamespace.Types,
309            XmlElementNames.Transitions);
310
311        for (TimeZoneTransition transition : this.transitions) {
312          transition.writeToXml(writer);
313        }
314
315        writer.writeEndElement(); // Transitions
316      }
317    }
318  }
319
320  /**
321   * Writes to XML.
322   *
323   * @param writer The writer.
324   * @throws Exception the exception
325   */
326  protected void writeToXml(EwsServiceXmlWriter writer) throws Exception {
327    this.writeToXml(writer, XmlElementNames.TimeZoneDefinition);
328  }
329
330  /**
331   * Validates this time zone definition.
332   *
333   * @throws InvalidOrUnsupportedTimeZoneDefinitionException thrown when time zone definition is not valid.
334   */
335  public void validate() throws ServiceLocalException {
336    // The definition must have at least one period, one transition group
337    // and one transition,
338    // and there must be as many transitions as there are transition groups.
339    if (this.periods.size() < 1 || this.transitions.size() < 1
340        || this.transitionGroups.size() < 1
341        || this.transitionGroups.size() != this.transitions.size()) {
342      throw new InvalidOrUnsupportedTimeZoneDefinitionException();
343    }
344
345    // The first transition must be of type TimeZoneTransition.
346    if (this.transitions.get(0).getClass() != TimeZoneTransition.class) {
347      throw new InvalidOrUnsupportedTimeZoneDefinitionException();
348    }
349
350    // All transitions must be to transition groups and be either
351    // TimeZoneTransition or
352    // AbsoluteDateTransition instances.
353    for (TimeZoneTransition transition : this.transitions) {
354      Class<?> transitionType = transition.getClass();
355
356      if (transitionType != TimeZoneTransition.class
357          && transitionType != AbsoluteDateTransition.class) {
358        throw new InvalidOrUnsupportedTimeZoneDefinitionException();
359      }
360
361      if (transition.getTargetGroup() == null) {
362        throw new InvalidOrUnsupportedTimeZoneDefinitionException();
363      }
364    }
365
366    // All transition groups must be valid.
367    for (TimeZoneTransitionGroup transitionGroup : this.transitionGroups
368        .values()) {
369      transitionGroup.validate();
370    }
371  }
372
373  /**
374   * Gets the name of this time zone definition.
375   *
376   * @return the name
377   */
378  public String getName() {
379    return this.name;
380  }
381
382  /**
383   * Sets the name.
384   *
385   * @param name the new name
386   */
387  protected void setName(String name) {
388    this.name = name;
389  }
390
391  /**
392   * Gets the Id of this time zone definition.
393   *
394   * @return the id
395   */
396  public String getId() {
397    return this.id;
398  }
399
400  /**
401   * Sets the id.
402   *
403   * @param id the new id
404   */
405  public void setId(String id) {
406    this.id = id;
407  }
408
409  /**
410   * Adds a transition group with a single transition to the specified period.
411   *
412   * @return A TimeZoneTransitionGroup.
413   */
414  public Map<String, TimeZonePeriod> getPeriods() {
415    return this.periods;
416  }
417
418  /**
419   * Gets the transition groups associated with this time zone definition,
420   * indexed by Id.
421   *
422   * @return the transition groups
423   */
424  public Map<String, TimeZoneTransitionGroup> getTransitionGroups() {
425    return this.transitionGroups;
426  }
427
428  /**
429   * Writes to XML.
430   *
431   * @param writer         accepts EwsServiceXmlWriter
432   * @param xmlElementName accepts String
433   * @throws Exception throws Exception
434   */
435  public void writeToXml(EwsServiceXmlWriter writer, String xmlElementName)
436      throws Exception {
437    this.writeToXml(writer, this.getNamespace(), xmlElementName);
438  }
439
440}