Commit c8fd1693a6e1f6414057c71b0d6ace6b2ccc9f69

Authored by 潘钊
1 parent 7a91135b

update...

src/main/java/com/bsth/data/schedule/DayOfSchedule.java
@@ -181,7 +181,7 @@ public class DayOfSchedule { @@ -181,7 +181,7 @@ public class DayOfSchedule {
181 181
182 182
183 Set<String> lps = searchAllLP(list); 183 Set<String> lps = searchAllLP(list);
184 - for(String lp : lps){ 184 + for (String lp : lps) {
185 //计算“起点站应到”时间 185 //计算“起点站应到”时间
186 schAttrCalculator.calcQdzTimePlan(lpScheduleMap.get(lineCode + "_" + lp)); 186 schAttrCalculator.calcQdzTimePlan(lpScheduleMap.get(lineCode + "_" + lp));
187 } 187 }
@@ -201,14 +201,14 @@ public class DayOfSchedule { @@ -201,14 +201,14 @@ public class DayOfSchedule {
201 return 0; 201 return 0;
202 } 202 }
203 203
204 - public int reloadSch(String lineCode){ 204 + public int reloadSch(String lineCode) {
205 return reloadSch(lineCode, calcSchDate(lineCode), true); 205 return reloadSch(lineCode, calcSchDate(lineCode), true);
206 } 206 }
207 207
208 /** 208 /**
209 * @Title: searchAllCars 209 * @Title: searchAllCars
210 * @Description: TODO(搜索班次集合中的车辆) 210 * @Description: TODO(搜索班次集合中的车辆)
211 - */ 211 + */
212 private Set<String> searchAllCars(List<ScheduleRealInfo> list) { 212 private Set<String> searchAllCars(List<ScheduleRealInfo> list) {
213 Set<String> cars = new HashSet<>(); 213 Set<String> cars = new HashSet<>();
214 for (ScheduleRealInfo sch : list) 214 for (ScheduleRealInfo sch : list)
@@ -277,12 +277,12 @@ public class DayOfSchedule { @@ -277,12 +277,12 @@ public class DayOfSchedule {
277 //清理路牌对照 277 //清理路牌对照
278 List<String> lprms = new ArrayList<>(); 278 List<String> lprms = new ArrayList<>();
279 Set<String> lps = lpScheduleMap.keySet(); 279 Set<String> lps = lpScheduleMap.keySet();
280 - for(String lp : lps){  
281 - if(lp.indexOf(lineCode + "_") != -1) 280 + for (String lp : lps) {
  281 + if (lp.indexOf(lineCode + "_") != -1)
282 lprms.add(lp); 282 lprms.add(lp);
283 } 283 }
284 284
285 - for(String lp : lprms) 285 + for (String lp : lprms)
286 lpScheduleMap.removeAll(lp); 286 lpScheduleMap.removeAll(lp);
287 287
288 logger.info(lineCode + "排班清理 " + count); 288 logger.info(lineCode + "排班清理 " + count);
@@ -336,10 +336,10 @@ public class DayOfSchedule { @@ -336,10 +336,10 @@ public class DayOfSchedule {
336 if (StringUtils.isEmpty(sch.getFcsj())) 336 if (StringUtils.isEmpty(sch.getFcsj()))
337 sch.setFcsj("00:00"); 337 sch.setFcsj("00:00");
338 338
339 - if(sch.getFcsj().equals("24:00")) 339 + if (sch.getFcsj().equals("24:00"))
340 sch.setFcsj("23:59"); 340 sch.setFcsj("23:59");
341 341
342 - if(sch.getFcsj().substring(0, 2).equals("24")){ 342 + if (sch.getFcsj().substring(0, 2).equals("24")) {
343 sch.setFcsj("00" + sch.getFcsj().substring(2)); 343 sch.setFcsj("00" + sch.getFcsj().substring(2));
344 } 344 }
345 345
@@ -364,13 +364,13 @@ public class DayOfSchedule { @@ -364,13 +364,13 @@ public class DayOfSchedule {
364 } 364 }
365 365
366 //售票员为空设置为""字符串 366 //售票员为空设置为""字符串
367 - if(StringUtils.isEmpty(sch.getsGh())){ 367 + if (StringUtils.isEmpty(sch.getsGh())) {
368 sch.setsGh(""); 368 sch.setsGh("");
369 sch.setsName(""); 369 sch.setsName("");
370 } 370 }
371 sch.setJhlcOrig(sch.getJhlc()); 371 sch.setJhlcOrig(sch.getJhlc());
372 //保留备注 372 //保留备注
373 - if(StringUtils.isNotEmpty(sch.getRemark())) 373 + if (StringUtils.isNotEmpty(sch.getRemark()))
374 sch.setRemarks(sch.getRemark()); 374 sch.setRemarks(sch.getRemark());
375 } 375 }
376 } catch (Exception e) { 376 } catch (Exception e) {
@@ -440,7 +440,7 @@ public class DayOfSchedule { @@ -440,7 +440,7 @@ public class DayOfSchedule {
440 440
441 Collection<ScheduleRealInfo> schs = id2SchedulMap.values(); 441 Collection<ScheduleRealInfo> schs = id2SchedulMap.values();
442 for (ScheduleRealInfo sch : schs) { 442 for (ScheduleRealInfo sch : schs) {
443 - if(lineList.contains(sch.getXlBm())){ 443 + if (lineList.contains(sch.getXlBm())) {
444 mMap.put(sch.getXlBm(), sch); 444 mMap.put(sch.getXlBm(), sch);
445 } 445 }
446 } 446 }
@@ -497,12 +497,13 @@ public class DayOfSchedule { @@ -497,12 +497,13 @@ public class DayOfSchedule {
497 497
498 /** 498 /**
499 * 下一个相同走向的班次 499 * 下一个相同走向的班次
  500 + *
500 * @param sch 501 * @param sch
501 * @return 502 * @return
502 */ 503 */
503 - public ScheduleRealInfo nextSame(final ScheduleRealInfo sch){ 504 + public ScheduleRealInfo nextSame(final ScheduleRealInfo sch) {
504 List<ScheduleRealInfo> list = nbbmScheduleMap.get(sch.getClZbh()); 505 List<ScheduleRealInfo> list = nbbmScheduleMap.get(sch.getClZbh());
505 - Collection<ScheduleRealInfo> subList = Collections2.filter(list, new Predicate<ScheduleRealInfo>(){ 506 + Collection<ScheduleRealInfo> subList = Collections2.filter(list, new Predicate<ScheduleRealInfo>() {
506 507
507 @Override 508 @Override
508 public boolean apply(ScheduleRealInfo item) { 509 public boolean apply(ScheduleRealInfo item) {
@@ -544,11 +545,12 @@ public class DayOfSchedule { @@ -544,11 +545,12 @@ public class DayOfSchedule {
544 545
545 /** 546 /**
546 * 下一个班次 547 * 下一个班次
  548 + *
547 * @param list 班次集合 549 * @param list 班次集合
548 - * @param sch 当前班次 550 + * @param sch 当前班次
549 * @return 551 * @return
550 */ 552 */
551 - private ScheduleRealInfo next(Collection<ScheduleRealInfo> list , ScheduleRealInfo sch){ 553 + private ScheduleRealInfo next(Collection<ScheduleRealInfo> list, ScheduleRealInfo sch) {
552 int outConfig = -1; 554 int outConfig = -1;
553 LineConfig config = lineConfigData.get(sch.getXlBm()); 555 LineConfig config = lineConfigData.get(sch.getXlBm());
554 if (config != null) 556 if (config != null)
@@ -583,11 +585,12 @@ public class DayOfSchedule { @@ -583,11 +585,12 @@ public class DayOfSchedule {
583 585
584 /** 586 /**
585 * 下一个班次 587 * 下一个班次
  588 + *
586 * @param list 班次集合 589 * @param list 班次集合
587 - * @param sch 当前班次 590 + * @param sch 当前班次
588 * @return 591 * @return
589 */ 592 */
590 - private ScheduleRealInfo next2(Collection<ScheduleRealInfo> list , ScheduleRealInfo sch){ 593 + private ScheduleRealInfo next2(Collection<ScheduleRealInfo> list, ScheduleRealInfo sch) {
591 int outConfig = -1; 594 int outConfig = -1;
592 LineConfig config = lineConfigData.get(sch.getXlBm()); 595 LineConfig config = lineConfigData.get(sch.getXlBm());
593 if (config != null) 596 if (config != null)
@@ -639,13 +642,14 @@ public class DayOfSchedule { @@ -639,13 +642,14 @@ public class DayOfSchedule {
639 642
640 /** 643 /**
641 * 是否是首班出场 644 * 是否是首班出场
  645 + *
642 * @param sch 646 * @param sch
643 * @return 647 * @return
644 */ 648 */
645 - public boolean isFirstOut(ScheduleRealInfo sch){ 649 + public boolean isFirstOut(ScheduleRealInfo sch) {
646 List<ScheduleRealInfo> list = nbbmScheduleMap.get(sch.getClZbh()); 650 List<ScheduleRealInfo> list = nbbmScheduleMap.get(sch.getClZbh());
647 try { 651 try {
648 - if(list.get(0) == sch && sch.getBcType().equals("out")) 652 + if (list.get(0) == sch && sch.getBcType().equals("out"))
649 return true; 653 return true;
650 } catch (IndexOutOfBoundsException e) { 654 } catch (IndexOutOfBoundsException e) {
651 logger.error("小小的数组越界,无伤大雅!"); 655 logger.error("小小的数组越界,无伤大雅!");
@@ -671,7 +675,7 @@ public class DayOfSchedule { @@ -671,7 +675,7 @@ public class DayOfSchedule {
671 save(sch); 675 save(sch);
672 } 676 }
673 677
674 - public void addLPMapp(ScheduleRealInfo sch){ 678 + public void addLPMapp(ScheduleRealInfo sch) {
675 lpScheduleMap.put(sch.getXlBm() + "_" + sch.getLpName(), sch); 679 lpScheduleMap.put(sch.getXlBm() + "_" + sch.getLpName(), sch);
676 } 680 }
677 681
@@ -685,7 +689,7 @@ public class DayOfSchedule { @@ -685,7 +689,7 @@ public class DayOfSchedule {
685 lpScheduleMap.remove(sch.getXlBm() + "_" + sch.getLpName(), sch); 689 lpScheduleMap.remove(sch.getXlBm() + "_" + sch.getLpName(), sch);
686 690
687 //如果正在执行该班次 691 //如果正在执行该班次
688 - if(carExecutePlanMap.get(sch.getClZbh()) == sch){ 692 + if (carExecutePlanMap.get(sch.getClZbh()) == sch) {
689 //重新计算车辆当前执行班次 693 //重新计算车辆当前执行班次
690 reCalcExecPlan(sch.getClZbh()); 694 reCalcExecPlan(sch.getClZbh());
691 } 695 }
@@ -823,12 +827,12 @@ public class DayOfSchedule { @@ -823,12 +827,12 @@ public class DayOfSchedule {
823 Collections.sort(list, schFCSJComparator); 827 Collections.sort(list, schFCSJComparator);
824 828
825 boolean flag = false; 829 boolean flag = false;
826 - for(ScheduleRealInfo temp : list){ 830 + for (ScheduleRealInfo temp : list) {
827 831
828 - if(flag && temp.getBcType().equals(bcType)) 832 + if (flag && temp.getBcType().equals(bcType))
829 return temp; 833 return temp;
830 834
831 - if(temp == sch){ 835 + if (temp == sch) {
832 flag = true; 836 flag = true;
833 } 837 }
834 } 838 }
@@ -837,21 +841,22 @@ public class DayOfSchedule { @@ -837,21 +841,22 @@ public class DayOfSchedule {
837 841
838 /** 842 /**
839 * 搜索离当前时间最近的一个指定类型的班次 843 * 搜索离当前时间最近的一个指定类型的班次
  844 + *
840 * @param nbbm 845 * @param nbbm
841 * @param bcType 846 * @param bcType
842 * @return 847 * @return
843 */ 848 */
844 - public ScheduleRealInfo searchNearByBcType(String nbbm, String bcType){ 849 + public ScheduleRealInfo searchNearByBcType(String nbbm, String bcType) {
845 List<ScheduleRealInfo> list = findByBcType(nbbm, bcType); 850 List<ScheduleRealInfo> list = findByBcType(nbbm, bcType);
846 Collections.sort(list, schFCSJComparator); 851 Collections.sort(list, schFCSJComparator);
847 852
848 long t = System.currentTimeMillis(); 853 long t = System.currentTimeMillis();
849 - int distance=-1, diff; 854 + int distance = -1, diff;
850 855
851 ScheduleRealInfo sch = null; 856 ScheduleRealInfo sch = null;
852 - for(ScheduleRealInfo temp : list){ 857 + for (ScheduleRealInfo temp : list) {
853 diff = (int) Math.abs(temp.getDfsjT() - t); 858 diff = (int) Math.abs(temp.getDfsjT() - t);
854 - if(diff < distance || distance == -1){ 859 + if (diff < distance || distance == -1) {
855 sch = temp; 860 sch = temp;
856 distance = diff; 861 distance = diff;
857 } 862 }
@@ -878,7 +883,7 @@ public class DayOfSchedule { @@ -878,7 +883,7 @@ public class DayOfSchedule {
878 } 883 }
879 884
880 public void addExecPlan(ScheduleRealInfo sch) { 885 public void addExecPlan(ScheduleRealInfo sch) {
881 - if(sch != null) 886 + if (sch != null)
882 carExecutePlanMap.put(sch.getClZbh(), sch); 887 carExecutePlanMap.put(sch.getClZbh(), sch);
883 else 888 else
884 carExecutePlanMap.remove(sch.getClZbh()); 889 carExecutePlanMap.remove(sch.getClZbh());
@@ -915,7 +920,7 @@ public class DayOfSchedule { @@ -915,7 +920,7 @@ public class DayOfSchedule {
915 nbbmScheduleMap.remove(sch.getClZbh(), sch); 920 nbbmScheduleMap.remove(sch.getClZbh(), sch);
916 921
917 sch.setClZbh(newClZbh); 922 sch.setClZbh(newClZbh);
918 - if(!nbbmScheduleMap.containsEntry(newClZbh, sch)){ 923 + if (!nbbmScheduleMap.containsEntry(newClZbh, sch)) {
919 nbbmScheduleMap.put(newClZbh, sch); 924 nbbmScheduleMap.put(newClZbh, sch);
920 } 925 }
921 926
@@ -929,52 +934,55 @@ public class DayOfSchedule { @@ -929,52 +934,55 @@ public class DayOfSchedule {
929 return ups; 934 return ups;
930 } 935 }
931 936
932 - public void removeNbbm2SchMapp(ScheduleRealInfo sch, String nbbm){ 937 + public void removeNbbm2SchMapp(ScheduleRealInfo sch, String nbbm) {
933 nbbmScheduleMap.remove(nbbm, sch); 938 nbbmScheduleMap.remove(nbbm, sch);
934 } 939 }
935 940
936 - public void addNbbm2SchMapp(ScheduleRealInfo sch, String nbbm){ 941 + public void addNbbm2SchMapp(ScheduleRealInfo sch, String nbbm) {
937 nbbmScheduleMap.put(nbbm, sch); 942 nbbmScheduleMap.put(nbbm, sch);
938 } 943 }
939 944
940 - public void reCalcExecPlan(String nbbm){ 945 + public void reCalcExecPlan(String nbbm) {
941 List<ScheduleRealInfo> list = nbbmScheduleMap.get(nbbm); 946 List<ScheduleRealInfo> list = nbbmScheduleMap.get(nbbm);
942 Collections.sort(list, schFCSJComparator); 947 Collections.sort(list, schFCSJComparator);
943 948
944 ScheduleRealInfo sch = schAttrCalculator.calcCurrentExecSch(list); 949 ScheduleRealInfo sch = schAttrCalculator.calcCurrentExecSch(list);
945 carExecutePlanMap.put(nbbm, sch); 950 carExecutePlanMap.put(nbbm, sch);
946 951
947 - if(sch==null){  
948 - logger.info("车辆" + nbbm + "无可执行任务,切换至非营运状态");  
949 - DirectivePushQueue.put6003(nbbm, 1, 0 , null, "系统");  
950 - return;  
951 - }  
952 try { 952 try {
  953 + //切换设备状态
953 GpsEntity gps = gpsRealData.get(BasicData.deviceId2NbbmMap.inverse().get(nbbm)); 954 GpsEntity gps = gpsRealData.get(BasicData.deviceId2NbbmMap.inverse().get(nbbm));
954 - if(gps != null && gps.isOnline()){  
955 - if(StringUtils.isNotEmpty(gps.getLineId()) && !gps.getLineId().equals(sch.getXlBm())){  
956 - //下发线路切换指令  
957 - DirectivePushQueue.put64(nbbm, sch.getXlBm(), "系统");  
958 - logger.info("车辆" + nbbm + "切换至" + sch.getXlBm() + " -原" + gps.getLineId() + " --重新计算当前执行班次!");  
959 - } 955 + if (gps == null || !gps.isOnline())
  956 + return;
  957 + if (sch == null) {
  958 + logger.info("车辆" + nbbm + "无可执行任务,切换至非营运状态");
  959 + DirectivePushQueue.put6003(nbbm, 1, 0, null, "系统");
  960 + return;
960 } 961 }
961 - }catch (Exception e){ 962 +
  963 + if (StringUtils.isNotEmpty(gps.getLineId()) && !gps.getLineId().equals(sch.getXlBm())) {
  964 + //下发线路切换指令
  965 + DirectivePushQueue.put64(nbbm, sch.getXlBm(), "系统");
  966 + logger.info("车辆" + nbbm + "切换至" + sch.getXlBm() + " -原" + gps.getLineId() + " --重新计算当前执行班次!");
  967 + }
  968 + } catch (Exception e) {
962 logger.error("", e); 969 logger.error("", e);
963 } 970 }
964 } 971 }
965 972
966 /** 973 /**
967 * 是否在执行首班出场 974 * 是否在执行首班出场
  975 + *
968 * @param nbbm 976 * @param nbbm
969 * @return 977 * @return
970 */ 978 */
971 - public boolean isExecFirstOut(String nbbm, long time){ 979 + public boolean isExecFirstOut(String nbbm, long time) {
972 try { 980 try {
973 List<ScheduleRealInfo> list = nbbmScheduleMap.get(nbbm); 981 List<ScheduleRealInfo> list = nbbmScheduleMap.get(nbbm);
974 ScheduleRealInfo second = list.get(2), 982 ScheduleRealInfo second = list.get(2),
975 first = executeCurr(nbbm); 983 first = executeCurr(nbbm);
976 984
977 - if(first.getBcType().equals("out") 985 + if (first.getBcType().equals("out")
978 && first.getDfsjT() < second.getDfsjT() 986 && first.getDfsjT() < second.getDfsjT()
979 && doneSum(nbbm) == 0 && second.getDfsjT() > time) 987 && doneSum(nbbm) == 0 && second.getDfsjT() > time)
980 return true; 988 return true;
@@ -988,8 +996,10 @@ public class DayOfSchedule { @@ -988,8 +996,10 @@ public class DayOfSchedule {
988 996
989 @Autowired 997 @Autowired
990 JdbcTemplate jdbcTemplate; 998 JdbcTemplate jdbcTemplate;
  999 +
991 /** 1000 /**
992 * 删除实际排班 1001 * 删除实际排班
  1002 + *
993 * @param lineCode 1003 * @param lineCode
994 * @return 1004 * @return
995 */ 1005 */
@@ -998,16 +1008,16 @@ public class DayOfSchedule { @@ -998,16 +1008,16 @@ public class DayOfSchedule {
998 1008
999 try { 1009 try {
1000 String rq = currSchDateMap.get(lineCode); 1010 String rq = currSchDateMap.get(lineCode);
1001 - if(StringUtils.isNotEmpty(rq)){ 1011 + if (StringUtils.isNotEmpty(rq)) {
1002 List<ScheduleRealInfo> all = findByLineCode(lineCode); 1012 List<ScheduleRealInfo> all = findByLineCode(lineCode);
1003 //解除gps 和班次之间的关联 1013 //解除gps 和班次之间的关联
1004 List<ScheduleRealInfo> unions = calcUnion(all, carExecutePlanMap.values()); 1014 List<ScheduleRealInfo> unions = calcUnion(all, carExecutePlanMap.values());
1005 - for(ScheduleRealInfo sch : unions){ 1015 + for (ScheduleRealInfo sch : unions) {
1006 removeExecPlan(sch.getClZbh()); 1016 removeExecPlan(sch.getClZbh());
1007 } 1017 }
1008 //解除调度指令和班次的外键约束 1018 //解除调度指令和班次的外键约束
1009 StringBuilder inStr = new StringBuilder("("); 1019 StringBuilder inStr = new StringBuilder("(");
1010 - for(ScheduleRealInfo sch : all){ 1020 + for (ScheduleRealInfo sch : all) {
1011 inStr.append(sch.getId() + ","); 1021 inStr.append(sch.getId() + ",");
1012 } 1022 }
1013 inStr.deleteCharAt(inStr.length() - 1).append(")"); 1023 inStr.deleteCharAt(inStr.length() - 1).append(")");
@@ -1019,10 +1029,10 @@ public class DayOfSchedule { @@ -1019,10 +1029,10 @@ public class DayOfSchedule {
1019 1029
1020 } 1030 }
1021 rs.put("status", ResponseCode.SUCCESS); 1031 rs.put("status", ResponseCode.SUCCESS);
1022 - }catch (Exception e){ 1032 + } catch (Exception e) {
1023 logger.error("", e); 1033 logger.error("", e);
1024 rs.put("status", ResponseCode.ERROR); 1034 rs.put("status", ResponseCode.ERROR);
1025 - if(e instanceof DataIntegrityViolationException) 1035 + if (e instanceof DataIntegrityViolationException)
1026 rs.put("msg", "失败,违反数据约束!!"); 1036 rs.put("msg", "失败,违反数据约束!!");
1027 else 1037 else
1028 rs.put("msg", e.getMessage()); 1038 rs.put("msg", e.getMessage());
@@ -1042,7 +1052,7 @@ public class DayOfSchedule { @@ -1042,7 +1052,7 @@ public class DayOfSchedule {
1042 List<ScheduleRealInfo> rs = new ArrayList<>(); 1052 List<ScheduleRealInfo> rs = new ArrayList<>();
1043 1053
1044 for (ScheduleRealInfo sch : c1) { 1054 for (ScheduleRealInfo sch : c1) {
1045 - if(c2.contains(sch)){ 1055 + if (c2.contains(sch)) {
1046 rs.add(sch); 1056 rs.add(sch);
1047 } 1057 }
1048 } 1058 }
@@ -1051,16 +1061,18 @@ public class DayOfSchedule { @@ -1051,16 +1061,18 @@ public class DayOfSchedule {
1051 1061
1052 /** 1062 /**
1053 * 覆盖一辆车的所有班次 1063 * 覆盖一辆车的所有班次
  1064 + *
1054 * @param nbbm 1065 * @param nbbm
1055 * @param sets 1066 * @param sets
1056 */ 1067 */
1057 - public void replaceByNbbm(String nbbm, Collection<ScheduleRealInfo> sets){ 1068 + public void replaceByNbbm(String nbbm, Collection<ScheduleRealInfo> sets) {
1058 nbbmScheduleMap.removeAll(nbbm); 1069 nbbmScheduleMap.removeAll(nbbm);
1059 nbbmScheduleMap.putAll(nbbm, sets); 1070 nbbmScheduleMap.putAll(nbbm, sets);
1060 } 1071 }
1061 1072
1062 /** 1073 /**
1063 * 获取该班次所在路牌的下一个班次 1074 * 获取该班次所在路牌的下一个班次
  1075 + *
1064 * @param sch 1076 * @param sch
1065 * @return 1077 * @return
1066 */ 1078 */
@@ -1072,6 +1084,7 @@ public class DayOfSchedule { @@ -1072,6 +1084,7 @@ public class DayOfSchedule {
1072 1084
1073 /** 1085 /**
1074 * 获取该班次所在路牌的下一个班次,不考虑场既是站 1086 * 获取该班次所在路牌的下一个班次,不考虑场既是站
  1087 + *
1075 * @param sch 1088 * @param sch
1076 * @return 1089 * @return
1077 */ 1090 */
@@ -1081,7 +1094,7 @@ public class DayOfSchedule { @@ -1081,7 +1094,7 @@ public class DayOfSchedule {
1081 return next2(list, sch); 1094 return next2(list, sch);
1082 } 1095 }
1083 1096
1084 - public ArrayListMultimap<String, ScheduleRealInfo> getLpScheduleMap(){ 1097 + public ArrayListMultimap<String, ScheduleRealInfo> getLpScheduleMap() {
1085 return lpScheduleMap; 1098 return lpScheduleMap;
1086 } 1099 }
1087 } 1100 }
1088 \ No newline at end of file 1101 \ No newline at end of file