1   package org.wcb.model.service.impl;
2   
3   import org.wcb.model.service.IValidatorService;
4   import org.wcb.model.vo.hibernate.Logbook;
5   import org.wcb.gui.util.ApplicationPreferences;
6   import org.wcb.gui.util.IPilotsLogApplicationPreferenceKeys;
7   
8   import java.util.List;
9   import java.util.Date;
10  
11  /**
12   * <small>
13   * Copyright (c)  2006  wbogaardt.
14   * Permission is granted to copy, distribute and/or modify this document
15   * under the terms of the GNU Free Documentation License, Version 1.2
16   * or any later version published by the Free Software Foundation;
17   * with no Invariant Sections, no Front-Cover Texts, and no Back-Cover
18   * Texts.  A copy of the license is included in the section entitled "GNU
19   * Free Documentation License".
20   * <p/>
21   * $File:  $ <br>
22   * $Change:  $ submitted by $Author: wbogaardt $ at $DateTime: Mar 29, 2006 8:21:52 AM $ <br>
23   * </small>
24   *
25   * @author wbogaardt
26   * @version 1
27   *          Date: Mar 29, 2006
28   *          Time: 8:21:52 AM
29   *
30   * //need to cover 121.471, 421.481, 135.265 already covered 61.57
31   */
32  
33  public class RecencyExperianceValidationService implements IValidatorService {
34  
35      private List<Logbook> allEntriesList;
36      // days for long value is in minutes
37      private static long DAYS90 = 129600;
38      private static long DAYS180 = 259200;
39      private StringBuilder messages;
40      private int countNightLanding = 0;
41      private int countDayLanding = 0;
42      private int countApproaches = 0;
43      private int countWarningNightLanding = 0;
44      private int countWarningDayLanding = 0;
45      private int countWarningApproaches = 0;
46      private int DAYSWARNING60 = 60;
47      private int DAYSWARNING150 = 150;
48      private int CURRENCY_90 = 90;
49      private int CURRENCY_180 = 180;
50  
51      public Object getObject() {
52          return allEntriesList;
53      }
54  
55      public void setObject(Object logObject) {
56          if (logObject instanceof List) {
57              this.allEntriesList = (List) logObject;
58          }
59      }
60  
61      public String getMessage() {
62          return messages.toString();
63      }
64  
65      /**
66       * Validates the logbook object for time, all fields entered.
67       * @return  Return true for successful validation or false for failed validation
68       */
69      public boolean validate() {
70          countNightLanding = 0;
71          countDayLanding = 0;
72          countApproaches = 0;
73          countWarningApproaches = 0;
74          countWarningDayLanding = 0;
75          countWarningNightLanding = 0;
76          boolean currentPassengers = false;
77          boolean currentNight = false;
78          boolean currentIFR = true;
79          boolean warningPassengers = false;
80          boolean warningIfr = false;
81          boolean warningNight = false;
82          for (Logbook objValue : allEntriesList) {
83              if (!isOver90Days(objValue.getEntryDate())) {
84  
85                  //check night currency in 90 days.
86                  checkCurrentNight(objValue);
87                  //check regular 90 day currency
88                  countDayLanding = countDayLanding + objValue.getDayLandings() + objValue.getNightLandings();
89                  if (!isOverUserDefinedDays(objValue.getEntryDate(), ApplicationPreferences.getInstance().getInt(IPilotsLogApplicationPreferenceKeys.DAYS_WARNING_CARRY_PASSENGERS, DAYSWARNING60))) {
90                      countWarningDayLanding = countWarningDayLanding + objValue.getDayLandings() + objValue.getNightLandings();
91                  }
92              }
93              // within last six months for instrument currency. First check that we need to check this.
94              if (ApplicationPreferences.getInstance().getBoolean(IPilotsLogApplicationPreferenceKeys.CHECK_IFR)) {
95                  if (!isOver180Days(objValue.getEntryDate())) {
96                      countApproaches = countApproaches + objValue.getInstrumentApproaches();
97                      if (!isOverUserDefinedDays(objValue.getEntryDate(), ApplicationPreferences.getInstance().getInt(IPilotsLogApplicationPreferenceKeys.DAYS_WARNING_IFR, DAYSWARNING150))) {
98                          countWarningApproaches = countWarningApproaches + objValue.getInstrumentApproaches();
99                      }
100                 }
101             }
102         }
103         messages = new StringBuilder();
104         /* Adds messages for display to the user */
105         currentNight = this.addMessageNightCurrency(messages, countNightLanding, countWarningNightLanding);
106         currentPassengers = this.addMessageCarryingDayPassengers(messages, countDayLanding, countWarningDayLanding);
107         if (ApplicationPreferences.getInstance().getBoolean(IPilotsLogApplicationPreferenceKeys.CHECK_IFR)) {
108             currentIFR = this.addMessageIFR(messages, countApproaches, countWarningApproaches);
109         }
110         return currentPassengers && currentNight && currentIFR;
111     }
112 
113     private void checkCurrentNight(Logbook obj) {
114         if(obj.getConditionNight() > 0) {
115             countNightLanding = countNightLanding + obj.getNightLandings();
116             if (!isOverUserDefinedDays(obj.getEntryDate(), ApplicationPreferences.getInstance().getInt(IPilotsLogApplicationPreferenceKeys.DAYS_WARNING_CARRY_NIGHT, 90))) {
117                 countWarningNightLanding = countWarningNightLanding + obj.getNightLandings();
118             }
119         }
120     }
121 
122     /**
123      * Adds message for night passenger carrying.
124      * @param messages The messages build that the warning or other messages goes to
125      * @param nightLanding The number of landings the person made during the FAR regulation period.
126      * @param warnNightLanding The number of landings they made during the user specified warning/notice period
127      * @return True if Pilot is current are current. False gets returned when warning happens or they are out of currency.
128      */
129     private boolean addMessageNightCurrency(StringBuilder messages, int nightLanding, int warnNightLanding) {
130         if (nightLanding > 3) {
131             messages.append("You are current for passenger carry on a night flight.\n \n");
132             if(warnNightLanding <= 3) {
133                 messages.append("WARNING: You will not be current to carry passengers at night in less then ");
134                 messages.append(CURRENCY_90 - ApplicationPreferences.getInstance().getInt(IPilotsLogApplicationPreferenceKeys.DAYS_WARNING_CARRY_NIGHT, DAYSWARNING60));
135                 messages.append(" days.\n");
136 				return false;
137             }
138             return true;
139         }
140         else {
141             messages.append("You are NOT night current.\n");
142             messages.append("FAR 61.57 \n");
143             messages.append("(b) Night takeoff and landing experience.\n (1) Except as provided in paragraph (e) of this section, \n  no person may act as pilot in command of an aircraft carrying passengers during the period beginning 1 hour after sunset and ending 1 hour before sunrise,\n   unless within the preceding 90 days that person has made at least three takeoffs and three landings to a full stop during the period beginning 1 hour after sunset and ending 1 hour before sunrise, and—\n");
144             messages.append("(i) That person acted as sole manipulator of the flight controls; and\n");
145             messages.append("(ii) The required takeoffs and landings were performed in an aircraft of the same category, class, and type (if a type rating is required).\n \n");
146         }
147         return false;
148     }
149 
150     /**
151      * IFR flights requires 6 proceedures in 6 calendar months.
152      * @param messages The messages build that the warning or other messages goes to
153      * @param approaches  The number of approaches the person made during the FAR regulation period.
154      * @param warningApproaches  The number of approaches they made during the user specified warning/notice period. False gets returned when warning happens or they are out of currency.
155      */
156     private boolean addMessageIFR(StringBuilder messages, int approaches, int warningApproaches) {
157         if (approaches >=6 ) {
158             messages.append("You are current for instrument flights. (FAR 61.57, C. Instrument Experience)\n \n");
159             if(warningApproaches < 6 ) {
160                 messages.append("WARNING: You will not be current for instruments in less then ");
161                 messages.append(CURRENCY_180 - ApplicationPreferences.getInstance().getInt(IPilotsLogApplicationPreferenceKeys.DAYS_WARNING_IFR, DAYSWARNING150));
162                 messages.append(" days.\n");
163 				return false;
164             }
165             return true;
166         }
167         else {
168             messages.append("You are not current for instruments.\n");
169             messages.append(" (c) Instrument experience. Except as provided in paragraph (e) of this section, no person may act as pilot in command under\n IFR or in weather conditions less than the minimums prescribed for VFR, unless within the preceding 6 calendar months, that person has:\n");
170             messages.append("(1) For the purpose of obtaining instrument experience in an aircraft (other than a glider), performed and logged under actual \n or simulated instrument conditions, either in flight in the appropriate category of aircraft for the instrument privileges sought or in a flight simulator or flight training device that is representative of the aircraft category for the instrument privileges sought—\n");
171             messages.append("(i) At least six instrument approaches;\n");
172             messages.append("(ii) Holding procedures; and\n");
173             messages.append("(iii) Intercepting and tracking courses through the use of navigation systems.\n \n");
174         }
175         return false;
176     }
177 
178     /**
179      * Adds message for day passenger carrying.
180      * @param messages The messages build that the warning or other messages goes to
181      * @param landings The number of landings the person made during the FAR regulation period.
182      * @param warningDay The number of landings they made during the user specified warning/notice period
183      * @return True if Pilot is current are current. False gets returned when warning happens or they are out of currency.
184      */
185     private boolean addMessageCarryingDayPassengers(StringBuilder messages, int landings, int warningDay) {
186         if (landings > 3) {
187             messages.append("You are current for passenger carry on DAY flight. (FAR 61.57 Recent Flight experience: Pilot in command.)\n \n");
188             if(warningDay <= 3) {
189                 messages.append("WARNING: You will not be current to carry passengers in less than ");
190                 messages.append(CURRENCY_90 - ApplicationPreferences.getInstance().getInt(IPilotsLogApplicationPreferenceKeys.DAYS_WARNING_CARRY_PASSENGERS, DAYSWARNING60));
191                 messages.append(" days.\n");
192 				return false;
193             }
194             return true;
195         }
196         else {
197             messages.append("You are NOT current to carry passengers. \n");
198             messages.append("FAR 61.57 \n");
199             messages.append("(a) General experience. (1) Except as provided in paragraph (e) of this section, no person may act as a pilot in command of an aircraft carrying passengers or of an aircraft certificated for more than one pilot flight crewmember unless that person has made at least three takeoffs and three landings within the preceding 90 days, and—\n");
200             messages.append("(i) The person acted as the sole manipulator of the flight controls; and\n");
201             messages.append("(ii) The required takeoffs and landings were performed in an aircraft of the same category, class, and type (if a type rating is required), and, if the aircraft to be flown is an airplane with a tailwheel, the takeoffs and landings must have been made to a full stop in an airplane with a tailwheel.\n");
202             messages.append("(2) For the purpose of meeting the requirements of paragraph (a)(1) of this section, a person may act as a pilot in command of an aircraft under day VFR or day IFR, provided no persons or property are carried on board the aircraft, other than those necessary for the conduct of the flight.\n");
203             messages.append("(3) The takeoffs and landings required by paragraph (a)(1) of this section may be accomplished in a flight simulator or flight training device that is—\n");
204             messages.append("(i) Approved by the Administrator for landings; and\n");
205             messages.append("(ii) Used in accordance with an approved course conducted by a training center certificated under part 142 of this chapter.\n \n");
206         }
207         return false;
208     }
209 
210     private boolean isOver90Days(Date d1) {
211         Date currentDate = new Date();
212         long diff = currentDate.getTime() - d1.getTime();
213         long minutes = diff / (1000 * 60);
214         return minutes > DAYS90;
215     }
216 
217     private boolean isOver180Days(Date d1) {
218         Date currentDate = new Date();
219         long diff = currentDate.getTime() - d1.getTime();
220         long minutes = diff / (1000 * 60);
221         return minutes > DAYS180;
222     }
223 
224     private boolean isOverUserDefinedDays(Date dl, int days) {
225         Date currentDate = new Date();
226         long diff = currentDate.getTime() - dl.getTime();
227         long minutes = diff / (1000 * 60);
228         long userDefinedTime = days * 1440;
229         return minutes > userDefinedTime;
230     }
231 }