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.EwsUtilities;
029import microsoft.exchange.webservices.data.core.XmlAttributeNames;
030import microsoft.exchange.webservices.data.core.XmlElementNames;
031import microsoft.exchange.webservices.data.core.exception.service.local.InvalidOrUnsupportedTimeZoneDefinitionException;
032import microsoft.exchange.webservices.data.core.exception.service.local.ServiceLocalException;
033import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlSerializationException;
034import microsoft.exchange.webservices.data.misc.TimeSpan;
035import microsoft.exchange.webservices.data.property.complex.ComplexProperty;
036
037import java.util.ArrayList;
038import java.util.List;
039
040/**
041 * Represents a group of time zone period transitions.
042 */
043public class TimeZoneTransitionGroup extends ComplexProperty {
044
045  /**
046   * The time zone definition.
047   */
048  private TimeZoneDefinition timeZoneDefinition;
049
050  /**
051   * The id.
052   */
053  private String id;
054
055  /**
056   * The transitions.
057   */
058  private List<TimeZoneTransition> transitions =
059      new ArrayList<TimeZoneTransition>();
060
061  /**
062   * The transition to standard.
063   */
064  private TimeZoneTransition transitionToStandard;
065
066  /**
067   * The transition to daylight.
068   */
069  private TimeZoneTransition transitionToDaylight;
070
071  /**
072   * The Constant PeriodTarget.
073   */
074  private final static String PeriodTarget = "Period";
075
076  /**
077   * The Constant GroupTarget.
078   */
079  private final static String GroupTarget = "Group";
080
081
082  /**
083   * Loads from XML.
084   *
085   * @param reader the reader
086   * @throws Exception the exception
087   */
088  public void loadFromXml(EwsServiceXmlReader reader) throws Exception {
089    this.loadFromXml(reader, XmlElementNames.TransitionsGroup);
090  }
091
092  /**
093   * Writes to XML.
094   *
095   * @param writer the writer
096   * @throws Exception the exception
097   */
098  public void writeToXml(EwsServiceXmlWriter writer) throws Exception {
099    this.writeToXml(writer, XmlElementNames.TransitionsGroup);
100  }
101
102  /**
103   * Reads the attribute from XML.
104   *
105   * @param reader the reader
106   * @throws Exception the exception
107   */
108  @Override
109  public void readAttributesFromXml(EwsServiceXmlReader reader)
110      throws Exception {
111    this.id = reader.readAttributeValue(XmlAttributeNames.Id);
112  }
113
114  /**
115   * Writes the attribute to XML.
116   *
117   * @param writer the writer
118   * @throws ServiceXmlSerializationException the service xml serialization exception
119   */
120  @Override
121  public void writeAttributesToXml(EwsServiceXmlWriter writer)
122      throws ServiceXmlSerializationException {
123    writer.writeAttributeValue(XmlAttributeNames.Id, this.id);
124  }
125
126  /**
127   * Writes the attribute to XML.
128   *
129   * @param reader the reader
130   * @return true, if successful
131   * @throws Exception the exception
132   */
133  @Override
134  public boolean tryReadElementFromXml(EwsServiceXmlReader reader)
135      throws Exception {
136    reader.ensureCurrentNodeIsStartElement();
137
138    TimeZoneTransition transition = TimeZoneTransition.create(
139        this.timeZoneDefinition, reader.getLocalName());
140
141    transition.loadFromXml(reader);
142
143    EwsUtilities
144        .ewsAssert(transition.getTargetPeriod() != null, "TimeZoneTransitionGroup.TryReadElementFromXml",
145                   "The transition's target period is null.");
146
147    this.transitions.add(transition);
148
149    return true;
150  }
151
152  /**
153   * Writes elements to XML.
154   *
155   * @param writer the writer
156   * @throws Exception the exception
157   */
158  @Override
159  public void writeElementsToXml(EwsServiceXmlWriter writer)
160      throws Exception {
161    for (TimeZoneTransition transition : this.transitions) {
162      transition.writeToXml(writer);
163    }
164  }
165
166  /**
167   * Validates this transition group.
168   *
169   * @throws InvalidOrUnsupportedTimeZoneDefinitionException thrown when time zone definition is not valid.
170   */
171  public void validate() throws ServiceLocalException {
172    // There must be exactly one or two transitions in the group.
173    if (this.transitions.size() < 1 || this.transitions.size() > 2) {
174      throw new InvalidOrUnsupportedTimeZoneDefinitionException();
175    }
176
177    // If there is only one transition, it must be of type
178    // TimeZoneTransition
179    if (this.transitions.size() == 1
180        && !(this.transitions.get(0).getClass() ==
181        TimeZoneTransition.class)) {
182      throw new InvalidOrUnsupportedTimeZoneDefinitionException();
183    }
184
185    // If there are two transitions, none of them should be of type
186    // TimeZoneTransition
187    if (this.transitions.size() == 2) {
188      for (TimeZoneTransition transition : this.transitions) {
189        if (transition.getClass() == TimeZoneTransition.class) {
190          throw new InvalidOrUnsupportedTimeZoneDefinitionException();
191        }
192      }
193    }
194
195    // All the transitions in the group must be to a period.
196    for (TimeZoneTransition transition : this.transitions) {
197      if (transition.getTargetPeriod() == null) {
198        throw new InvalidOrUnsupportedTimeZoneDefinitionException();
199      }
200    }
201  }
202
203  /**
204   * The Class CustomTimeZoneCreateParams.
205   */
206  protected static class CustomTimeZoneCreateParams {
207
208    /**
209     * The base offset to utc.
210     */
211    private TimeSpan baseOffsetToUtc;
212
213    /**
214     * The standard display name.
215     */
216    private String standardDisplayName;
217
218    /**
219     * The daylight display name.
220     */
221    private String daylightDisplayName;
222
223    /**
224     * Initializes a new instance of the class.
225     */
226    protected CustomTimeZoneCreateParams() {
227    }
228
229    /**
230     * Gets  the base offset to UTC.
231     *
232     * @return the base offset to utc
233     */
234    protected TimeSpan getBaseOffsetToUtc() {
235      return this.baseOffsetToUtc;
236    }
237
238    /**
239     * Sets the base offset to utc.
240     *
241     * @param baseOffsetToUtc the new base offset to utc
242     */
243    protected void setBaseOffsetToUtc(TimeSpan baseOffsetToUtc) {
244      this.baseOffsetToUtc = baseOffsetToUtc;
245    }
246
247    /**
248     * Gets the display name of the standard period.
249     *
250     * @return the standard display name
251     */
252    protected String getStandardDisplayName() {
253      return this.standardDisplayName;
254    }
255
256    /**
257     * Sets the standard display name.
258     *
259     * @param standardDisplayName the new standard display name
260     */
261    protected void setStandardDisplayName(String standardDisplayName) {
262      this.standardDisplayName = standardDisplayName;
263    }
264
265    /**
266     * Gets the display name of the daylight period.
267     *
268     * @return the daylight display name
269     */
270    protected String getDaylightDisplayName() {
271      return this.daylightDisplayName;
272    }
273
274    /**
275     * Sets the daylight display name.
276     *
277     * @param daylightDisplayName the new daylight display name
278     */
279    protected void setDaylightDisplayName(String daylightDisplayName) {
280      this.daylightDisplayName = daylightDisplayName;
281    }
282
283    /**
284     * Gets a value indicating whether the custom time zone should have a
285     * daylight period. <value> <c>true</c> if the custom time zone should
286     * have a daylight period; otherwise, <c>false</c>. </value>
287     *
288     * @return the checks for daylight period
289     */
290    protected boolean getHasDaylightPeriod() {
291      return (!(this.daylightDisplayName == null ||
292          this.daylightDisplayName.isEmpty()));
293    }
294  }
295
296  /**
297   * Gets a value indicating whether this group contains a transition to the
298   * Daylight period. <value><c>true</c> if this group contains a transition
299   * to daylight; otherwise, <c>false</c>.</value>
300   *
301   * @return the supports daylight
302   */
303  protected boolean getSupportsDaylight() {
304    return this.transitions.size() == 2;
305  }
306
307  /**
308   * Initializes the private members holding references to the transitions to
309   * the Daylight and Standard periods.
310   *
311   * @throws InvalidOrUnsupportedTimeZoneDefinitionException thrown when time zone definition is not valid.
312   */
313  private void initializeTransitions() throws ServiceLocalException {
314    if (this.transitionToStandard == null) {
315      for (TimeZoneTransition transition : this.transitions) {
316        if (transition.getTargetPeriod().isStandardPeriod() ||
317            (this.transitions.size() == 1)) {
318          this.transitionToStandard = transition;
319        } else {
320          this.transitionToDaylight = transition;
321        }
322      }
323    }
324
325    // If we didn't find a Standard period, this is an invalid time zone
326    // group.
327    if (this.transitionToStandard == null) {
328      throw new InvalidOrUnsupportedTimeZoneDefinitionException();
329    }
330  }
331
332  /**
333   * Gets the transition to the Daylight period.
334   *
335   * @return the transition to daylight
336   * @throws ServiceLocalException the service local exception
337   */
338  private TimeZoneTransition getTransitionToDaylight()
339      throws ServiceLocalException {
340    this.initializeTransitions();
341    return this.transitionToDaylight;
342  }
343
344  /**
345   * Gets the transition to the Standard period.
346   *
347   * @return the transition to standard
348   * @throws ServiceLocalException the service local exception
349   */
350  private TimeZoneTransition getTransitionToStandard()
351      throws ServiceLocalException {
352    this.initializeTransitions();
353    return this.transitionToStandard;
354  }
355
356  /**
357   * Gets the offset to UTC based on this group's transitions.
358   *
359   * @return the custom time zone creation params
360   */
361  protected CustomTimeZoneCreateParams getCustomTimeZoneCreationParams() {
362    CustomTimeZoneCreateParams result = new CustomTimeZoneCreateParams();
363
364    if (this.transitionToDaylight != null) {
365      result.setDaylightDisplayName(this.transitionToDaylight
366          .getTargetPeriod().getName());
367    }
368
369    result.setStandardDisplayName(this.transitionToStandard
370        .getTargetPeriod().getName());
371
372    // Assume that the standard period's offset is the base offset to UTC.
373    // EWS returns a positive offset for time zones that are behind UTC, and
374    // a negative one for time zones ahead of UTC. TimeZoneInfo does it the
375    // other
376    // way around.
377    // result.BaseOffsetToUtc =
378    // -this.TransitionToStandard.TargetPeriod.Bias;
379
380    return result;
381  }
382
383  /**
384   * Initializes a new instance of the class.
385   *
386   * @param timeZoneDefinition the time zone definition
387   */
388  public TimeZoneTransitionGroup(TimeZoneDefinition timeZoneDefinition) {
389    super();
390    this.timeZoneDefinition = timeZoneDefinition;
391  }
392
393  /**
394   * Initializes a new instance of the class.
395   *
396   * @param timeZoneDefinition the time zone definition
397   * @param id                 the id
398   */
399  public TimeZoneTransitionGroup(TimeZoneDefinition timeZoneDefinition, String id) {
400    this(timeZoneDefinition);
401    this.id = id;
402  }
403
404  /**
405   * Gets the id of this group.
406   *
407   * @return the id
408   */
409  public String getId() {
410    return this.id;
411  }
412
413  /**
414   * Sets the id.
415   *
416   * @param id the new id
417   */
418  public void setId(String id) {
419    this.id = id;
420  }
421
422  /**
423   * Gets the transitions in this group.
424   *
425   * @return the transitions
426   */
427  public List<TimeZoneTransition> getTransitions() {
428    return this.transitions;
429  }
430}