JAVA8: Find the file with the newest date in its filename - java

I have few files in an FTP folder with names like:
mainimport_31052017.csv
mainimport_21052017.csv
mainimport_15052017.csv
And I have a pattern string:
String pattern = "mainimport_ddmmyyy";
Now I am supposed to download the file with the latest date in its title. I am supposed to do it with Java 8 goodies.
I have a solution, but this is not pretty enough, I am doing it in 2 statements:
1) I first get the newest date:
Date newestDate = Collections.max(ftpFiles.stream().filter(fileName -> StringUtils.startsWith(fileName.getName(), prefix)).map(fileName -> {
String fileNameSuffix = fileName.getName().split("_")[1];
Date date = null;
try {
date = dateFormat.parse(fileNameSuffix);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}).collect(Collectors.toList()));
2) I then get the filename with the latest date:
Optional<FTPFile> file = ftpFiles.stream().filter(fileName->{
String fileNameSuffix = fileName.getName().split("_")[1];
Date date = null;
try {
date = dateFormat.parse(fileNameSuffix);
} catch (ParseException e) {
e.printStackTrace();
}
return StringUtils.startsWith(fileName.getName(), prefix) && date.equals(newestDate);
}).findFirst();
I am trying to do this both in a single statement, if it is possible.

Assuming that the dates always have the specified six-character representation, you may use
Optional<FTPFile> max = ftpFiles.stream()
.filter(file -> file.getName().startsWith(prefix))
.max(Comparator.comparing(file -> file.getName()
.replaceFirst(".*_([0-9]{2})([0-9]{2})([0-9]{4}).*", "$3$2$1")));
The factory method Comparator.comparing allows you to create a Comparator based on a property, so that the maximum element will be the element with the maximum value for that property.
Note that this simply converts the ddmmyyyy formatted date to a yyyymmdd string which can be compared lexicographically, which works as long as the day and months always have a two-digit form, i.e. with a leading zero.
You may optimize this a bit by preparing and reusing the regex pattern:
Pattern pattern = Pattern.compile(".*_([0-9]{2})([0-9]{2})([0-9]{4}).*");
Optional<FTPFile> max = ftpFiles.stream()
.filter(file -> file.getName().startsWith(prefix))
.max(Comparator.comparing(file ->
pattern.matcher(file.getName()).replaceFirst("$3$2$1")));
If the DateFormat is an unavoidable prerequisite, you may use
Optional<FTPFile> max = ftpFiles.stream()
.filter(file -> file.getName().startsWith(prefix))
.max(Comparator.comparing(file -> {
String name = file.getName();
name = name.substring(name.indexOf('_')+1);
try {
return dateFormat.parse(name);
} catch (ParseException e) {
throw new IllegalArgumentException(e);
}
}));
This performs the operation in one go, but has the disadvantage of performing the parsing operation more than necessary during the comparisons. If you want to avoid that, you may resort to the original two pass design, but you still don’t need to collect into a List:
ftpFiles.stream()
.map(FTPFile::getName)
.filter(name -> name.startsWith(prefix))
.map(name -> {
name = name.substring(name.indexOf('_')+1);
try {
return dateFormat.parse(name);
} catch (ParseException e) {
throw new IllegalArgumentException(e);
}
})
.max(Comparator.naturalOrder())
.map(date -> prefix+'_'+dateFormat.format(date))
.flatMap(fileName -> ftpFiles.stream()
.filter(file -> file.getName().equals(fileName)).findAny())
.ifPresent(System.out::println);

Related

java collec to list ignored

I am trying to write lambda in java that filter list by month and add data in the current month to the new list but when I try to collect the data I get an error collect is ignored.
public String getMonthlyExpensesNew() {
Functions functions = new Functions();
List<ShoppingMgnt> monthlyData = new ArrayList<>();
try {
monthlyData = getRecordsAsList();
monthlyData.stream().filter(date -> functions.checkForCurrentMonth(date.getPurchaseDate())).collect(Collectors.toList());
}catch (SQLException sqlException){
System.err.println("Error in getMonthlyExpensesNew");
}
return String.valueOf(monthlyData);
}
public boolean checkForCurrentMonth(String givenDate){
LocalDate currentDate = LocalDate.now();
LocalDate monthToCheck = LocalDate.parse(givenDate);
return currentDate.getMonth().equals(monthToCheck.getMonth());
}
your final function will be as follow:
public String getMonthlyExpensesNew() {
Functions functions = new Functions();
List<ShoppingMgnt> monthlyData = new ArrayList<>();
try {
monthlyData = getRecordsAsList();
//put the returned list in the same defined list
monthlyData = monthlyData.stream()
.filter(date -> functions.checkForCurrentMonth(date.getPurchaseDate()))
.collect(Collectors.toList());
}catch (SQLException sqlException){
System.err.println("Error in getMonthlyExpensesNew");
}
//the return with updated list
return String.valueOf(monthlyData);
}
Your initial code:
monthlyData.stream()
.filter(date -> functions.checkForCurrentMonth(date.getPurchaseDate()))
.collect(Collectors.toList());
Within this line the collect operation returns a List. You should store this List into your monthlyData reference to be returned later. So you should write like this:
monthlyData = monthlyData.stream()
.filter(date -> functions.checkForCurrentMonth(date.getPurchaseDate()))
.collect(Collectors.toList());

Sorting files working locally but not on kubernetes

I have about 1000 files with a date in their name, i would like to sort them by this date in the filename and pick the latest one which has a date same or earlier than an argument.
I have wrote this:
Pattern PATTERN = Pattern.compile("^\\d{4}-\\d{2}-\\d{2}-file.csv");
try {
deviceFiles = Files.list(filesDir.toPath())
.filter(path -> PATTERN.matcher(path.getFileName().toString()).matches()
&& !getDate(path).isAfter(ARGUMENT_DATE))
.collect(Collectors.toList());
Arrays.sort(deviceFiles.toArray(), new FileNamePathDateComparator());
logger.info("All files found are " + deviceFiles.stream().map(stream -> stream.toAbsolutePath().toString()).collect(Collectors.joining("\n")));
if (deviceFiles.isEmpty())
throw new IllegalStateException("There were no device files found");
else {
String deviceFilePath = deviceFiles.get(deviceFiles.size() - 1).toAbsolutePath().toString();
logger.info("Found device file: " + deviceFilePath);
return deviceFilePath;
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
The getDate method:
private LocalDate getDate(Path path)
{
try {
String[] parts = path.getFileName().toString().split("-");
return LocalDate.of(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]), Integer.parseInt(parts[2]));
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("bye", ex);
}
}
The comparator:
class FileNamePathDateComparator implements Comparator{
#Override
public int compare(Object o1, Object o2)
{
return getDate((Path)o1).compareTo(getDate((Path)o2));
}
}
When i run this locally i see that the logger prints all the files correctly sorted, the comparator works just fine.
But on a kubernetes cluster the files are printed randomly, i dont understand this.
Fixed! I have put the comparator in the stream rather than in the final list, and it works fine.
If in the mean while someone can provide an explanation, i would appreciate.
deviceFiles = Files.list(filesDir.toPath())
.filter(path -> PATTERN.matcher(path.getFileName().toString()).matches()
&& !getDate(path).isAfter(executionDate))
.sorted(new FileNamePathDateComparator()::compare)
.collect(Collectors.toList());

handle exceptions in stream java 8

I have a string:
"1, 2, 3 , -4"
it is split by ", ".
I have a function to convert a number to a enum type which works fine. I want to use java 8 to convert this string to a list of enum objects.
Pattern pattern = Pattern.compile(", ");
List<f> fList = pattern.splitAsStream(str)
.map(s -> {
try {
return this.getEnumObject(Integer.valueOf(s), f.class);
}
catch (NoEleException e) {
e.printStackTrace();
}
})
.collect(Collectors.toList());
This gives me an error:
missing return type.
How could I fix it?
Currently, if an exception occurs no result will be returned hence the compilation error. You'll need to return a value after the catch block .
Basically to ways of managing this:
catching the exception and return some value or encapsulate values in Optionals and filter accordingly
Throwing a RuntimeException which chains the original one
In the first case we use Optional to put something into the stream on error, and then manage these empty values further in the stream:
pattern.splitAsStream(str)
.map(s -> {
try {
return Optional.of(this.getEnumObject(Integer.valueOf(s), f.class));
}
catch (NoEleException e) {
e.printStackTrace();
return Optional.empty();
}
})
.filter(Optional::isPresent) // remove empty optionals
.map(Optional::get) // unwrap them
.collect(Collectors.toList());
In the second case the stream is stopped and you can then try to catch the RuntimeException and unchain the original one:
pattern.splitAsStream(str)
.map(s -> {
try {
return Optional.of(this.getEnumObject(Integer.valueOf(s), f.class));
}
catch (NoEleException e) {
e.printStackTrace();
throw new RuntimeException(e); // stop the stream
}
})
.collect(Collectors.toList());
You can create Null Object like MissingElement, return it in catch and them filter it out after map.
If you are certain that this won't happen you could return null in the catch and filter for non null before collecting:
Pattern pattern = Pattern.compile(", ");
List<f> fList = pattern.splitAsStream(str)
.map(s -> {
try {
return this.getEnumObject(Integer.valueOf(s), f.class);
}
catch (Exception e) {
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());

How to sort String[] in List by timestamp?

List<String[]> data = Importer.readCsv("test.csv");
A String[] item looks like this:
Position: name (String)
Position: 2017-03-14 18:22:44.149 (String)
What is a good approach in order to sort the list by the timestamp descending? I search for a solution without creating new Objects.
Use Collection.sort() and Comparator.comparing(), I find those easy to read when coming back to it later:
data.sort(Comparator.comparing((String[] o) -> o[1]).reversed());
You can compare the timestamp without conversion, as the format is "correctly" ordering the fields.
If you are confined to Java 7, there will be more boilerplate:
Collections.sort(data, new Comparator<String[]>()
{
#Override
public int compare(String[] o1, String[] o2)
{
return o2[1].compareTo(o1[1]);
// return -o1[1].compareTo(o2[1]);
}
});
Note that I need to compare o2 to o1 in this order or negate the result of comparing o1 to o2 to order the data descending. This becomes much more obvious in the first approach.
Well, thank you very much for your suggestions and impulses.
Special Thanks goes to XtremeBaumer!
Following code does the job too:
List<String[]> data = importer.readCsv(context, csvFile);
Collections.sort(data, new Comparator<String[]>() {
#Override
public int compare(String[] item1, String[] item2) {
String dateString1 = item1[1];
String dateString2 = item2[1];
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
Date date1 = null;
Date date2 = null;
try {
date1 = format.parse(dateString1);
date2 = format.parse(dateString2);
} catch (ParseException e) {
e.printStackTrace();
return 0;
}
// dateString1 is an earlier date than dateString2
return date1.compareTo(date2);
}
});
You could use a custom Comparator that takes the second element in each array and converts it to a Timestamp. Note that a Comparator's function can't throw an exception, so you'd have to catch the potential ParseException and deal with it:
Function<String[], Date> parser = s -> {
try {
return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SSS").parse(s[1]);
} catch (ParseException e) {
return null;
}
};
list.sort(Comparator.comparing(parser));

Avoiding a particular check for YYYY-MM--dd format in date

I have below method in which different date patterns have been handled
below is the method in which different date formats have been handled now
now for the particulat format YYYY-MM-dd i don't want it to go for the check where we are prefixing 20 before in code please advise how can i skip that part lets say if the date pattern is YYYY-MM-dd then avoid the logic of prefixing 20 in front of year
below is my code
public java.util.Date extractDate(String dateStr, String dateType) {
String[] datePatternsOfUk = { "d-M-yy", "d-M-yyyy", "d/M/yy", "d/M/yyyy", "yyyy-MM-dd","dd-MM-yy", "dd-MMM-yy","dd-MMM-yyyy","dd-MM-yyyy",
"dd/MM/yy","dd/MMM/yy","dd/MMM/yyyy"};
String[] datePatternsOfUs = { "M-d-yy","MM-dd-yy","M/d/yy","MM/dd/yy", "MM/dd/yy", "MMM-dd-yy",
"MMM/dd/yy", "MMM-dd-yyyy", "MM-dd-yyyy", "MMM/dd/yyyy",
"MM/dd/yyyy" };
java.util.Date date = null;
String[] datePatterns = datePatternsOfUk;
if (dateType.equals("US")) {
datePatterns = datePatternsOfUs;
} else if (dateType.equals("UK")) {
datePatterns = datePatternsOfUk;
}
///******code should not go in this check where date pattern is YYYY-MM-dd
int p = dateStr.lastIndexOf("/");
if (p == -1) {
p = dateStr.lastIndexOf("-");
}
String firstSubstring = dateStr.substring(0, p + 1);
String secondSubstring = dateStr.substring(p + 1);
if (p != -1 && secondSubstring.length() <= 2) {
secondSubstring = Integer.toString(2000 + Integer.parseInt(secondSubstring));
dateStr = firstSubstring + secondSubstring;
}
///****************************************//
try {
date = DateUtils.parseDate(dateStr, datePatterns);
} catch (ParseException ex) {
logger.error("##$$$$$### Error in invoice inside extractDate method : ##$$$$$$#### "
+ ErrorUtility.getStackTraceForException(ex));
}
return date;
}
You could avoid trying any inappropriate pattern by checking if the string "looks like" the pattern before parsing with the pattern.
The general way to do this is:
String datePattern = "yyyy-MM-dd"; // for example
String input;
if (input.matches(datePattern.replaceAll("\\w", "\\d"))) {
// the input looks like the pattern
// in this example "dddd-dd-dd" where "d" is any digit
// so go ahead and try the parse
}
You can enhance this logic to add:
if (input.matches("\\d\\d\\D.*")) {
// then it only has a two digit year, so add "20" to the front
}
if (!dateStr.equals("YYYY-MM-dd")) {
// code
}

Categories