I'm not sure how to efficiently re-factor the below code.
In the code below, there are 2 methods which each write data to a file. They are both quite similar except for:
First method accepts a single object parameter and then calls an external method for the write
Second method accepts an ArrayList of the objects as a paramter and loops over the ArrayList to perform the writes, directly invoking the writer.
Common sense tells me that this can be re-factored into a single write method which these two methods would then call, but not sure how to achieve this.
public void updateAccount(Account account) {
String outputString = outputStringCreator(account);
writeOutputString(outputString);
}
public void updateAccounts(ArrayList<Account> accounts) {
BufferedWriter writer = null;
try {
writer = new BufferedWriter(new FileWriter(filePath));
for(Account account : accounts) {
writer.write(outputStringCreator(account));
writer.newLine();
}
} catch (IOException e) {
System.out.println("IO issue: " + e.getMessage());
}
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
System.out.println("Couldn't close writer: " + e.getMessage());
}
}
}
public void writeOutputString(String outputString) {
BufferedWriter writer = null;
try {
writer = new BufferedWriter(new FileWriter(filePath));
writer.write(outputString);
writer.newLine();
} catch (IOException e) {
System.out.println("IO issue: " + e.getMessage());
}
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
System.out.println("Couldn't close writer: " + e.getMessage());
}
}
}
You can easily simplify overloading methods where one takes single parameter and other takes collection like this:
void foo( ObjectType object ) {
//pack object into singleton list
foo( List.of( object ) );
}
void foo( List< ObjectType > objects ) {
//execute logic on collection
}
Your method that takes collection should be able to handle cases where list has one element. That way you can easily just pass singleton list from overloading method and write only single algorithm.
Related
I have PVPStats objects stored in PlayerMeta.java:
public static Map <UUID, PVPstats> sPVPStats = new HashMap<>();
I know for sure the map is getting populated with objects that contain the expected vars for each uuid.
I'm trying to write these objects (converted to single lines of strings) into plugins/core/killstats.txt when the server calls onDisable() in Main.java
Along with the Map object, in PlayerMeta.java are also the methods to update and retrieve PVPStats objects from the Map. Those are all working.
The part that is not is working is the write method:
public static void writePVPStats() throws IOException {
BufferedWriter w = new BufferedWriter(new FileWriter("plugins/core/killstats.txt"));
sPVPStats.keySet().forEach(user -> {
try {
System.out.println(sPVPStats);
// stdout = {a6b6e3a1-a1ec-4fee-9d6d-f5e495c3e9d7=a6b6e3a1-a1ec-4fee-9d6d-f5e495c3e9d7:1:0}
w.write(user.toString() + "\n");
w.flush();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
w.close();
}
kill.txt after onDisable() is done:
a6b6e3a1-a1ec-4fee-9d6d-f5e495c3e9d7
Instead it needs to be:
{a6b6e3a1-a1ec-4fee-9d6d-f5e495c3e9d7=a6b6e3a1-a1ec-4fee-9d6d-f5e495c3e9d7:1:0}
For reference, here is the complete PVPStats class.
Lastly, in case it matters / helps, the reader on server launch:
Files.readAllLines(killstats_user_database.toPath()).forEach(line -> {
PVPstats stats = PVPstats.fromString(line);
PlayerMeta.sPVPStats.put(stats.playerid, stats);
});
Source Code:
backend.FileManager.java
backend.PlayerMeta.java
backend.PVPstats.java
events.PVP.java
EDIT
I just tried this with killstats.txt file type nad killstats.txt doesnt have anything in it now.
public static void writePVPStats() throws IOException {
BufferedWriter w = new BufferedWriter(new FileWriter("plugins/core/killstats.txt"));
for (PVPstats object: sPVPStats.values()) {
try {
System.out.println(sPVPStats);
w.write(object.toString() + "\n");
w.flush();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
};
w.close();
}
Ok so there were multiple issues. I wasn't controlling the flushing of the buffer, I declared the hasmap incorrectly, I wasn't accessing the value part of the hash map, and I wasn't correctly enforcing plain text.
SOLUTION
public static Map <UUID, PVPstats> sPVPStats = new HashMap<UUID, PVPstats>();
public static void writePVPStats() throws IOException {
BufferedWriter w = new BufferedWriter(new FileWriter("plugins/core/killstats.txt"));
for (PVPstats object: sPVPStats.values()) {
try {
System.out.println(sPVPStats);
System.out.println(object.toString());
w.write(object.toString() + "\n");
w.flush();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
};
w.close();
}
I have a buffered writer which is an instance of FileWriter in java. I have some functions which work like this:
private void a() {
try {
fileMaker("A");
bufferedWriter.write("x");
b();
bufferedWriter.write("z");
} catch (IOException e) {
}
}
private void b() {
try {
fileMaker("B");
bufferedWriter.write("b");
} catch (IOException e) {
}
private FileWriter bufferedWriter;
private void fileMaker(String fileName) {
try {
bufferedWriter = new FileWriter("./artifact/" + fileName + ".txt");
} catch (IOException e) {
System.out.println("there is something wrong in classFileMaker");
}
}
So my problem is, somehow after returning from function b I get a exception and can't write to file "A".
Any idea why?
fileMaker reuses the same bufferedWriter member, so every time you call it you lose the reference to the previous file's writer. A better design would be to return a new instance of a writer from the method and have the caller manage it:
private static FileWriter fileMaker(String fileName) throws IOException {
return new FileWriter("./artifact/" + fileName + ".txt");
}
private void a() {
try (FileWriter bufferedWriter = fileMaker("A")) {
bufferedWriter.write("x");
b();
bufferedWriter.write("z");
} catch (IOException e) {
}
}
private void b() {
try (FileWriter bufferedWriter = fileMaker("B")) {
bufferedWriter.write("b");
} catch (IOException e) {
}
}
my code has to read in two different Object Types (Bestellung, AKunde) through a ObjectOutputStream and save it in a csv file, which works.
But when i try to read them from the file it doesn't work.
Here is the code:
OutputStream:
LinkedList<Bestellung> bestellListe = verwaltungBestell.getBestellListe();
try {
fileOutputStream = new FileOutputStream(file);
outputStream = new ObjectOutputStream(fileOutputStream);
for (AKunde kunde : kundenliste) {
outputStream.writeObject(kunde);
}
for (Bestellung bestellung : bestellListe) {
outputStream.writeObject(bestellung);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileOutputStream != null) {
fileOutputStream.close();
}
if (outputStream != null) {
outputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
InputStream:
ArrayList<AKunde> kundenImport = new ArrayList<AKunde>();
ArrayList<Bestellung> bestellungenImport = new ArrayList<Bestellung>();
boolean cont = true;
try {
ObjectInputStream objectStream = new ObjectInputStream(new FileInputStream(directorie));
while (cont) {
AKunde kunde = null;
try {
kunde = (AKunde) objectStream.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
if (kunde != null) {
kundenImport.add(kunde);
} else {
cont = false;
}
}
while (cont) {
Bestellung bestellung = null;
try {
bestellung = (Bestellung) objectStream.readObject();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
if (bestellung != null) {
bestellungenImport.add(bestellung);
} else {
cont = false;
}
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
}
But it won't read the "Bestellungen" and won't save them into "bestellungenImport".
Anyone has a solution???
Your code never reaches the Bestellung reader part.
You have a false assumption that kunde =(AKunde)objectStream.readObject(); returns null.
Instead, it throws exception.
Oneway you can do is cast it like #luk2302.
Another way is to add a object count when writing your object stream:
outputStream.writeInt(kundenliste.size());
for (AKunde kunde : kundenliste) {
outputStream.writeObject(kunde);
}
outputStream.writeInt(bestellListe.size());
for (Bestellung bestellung : bestellListe) {
outputStream.writeObject(bestellung);
}
Then replace your while(cont) loop with a for each loop:
int kundeCount = objectStream.readInt();
for (int i = 0; i < kundeCount; i++) {
// Read and import kunde
}
You need to change your logic for reading objects. There are two main issues:
you never reset cont so the second while loop will never do anything
even if you did that you would always skip the first Bestellung since it was already read when the second loop is reached
I would propose something along the lines of:
Object object = objectStream.readObject();
if (object instanceof AKunde) {
kundenImport.add((AKunde) object);
} else if (object instanceof Bestellung) {
bestellungenImport.add((Bestellung) object);
} else {
// something else was read
}
You simply need to loop over this code and add proper error handling where needed.
I would suggest, you change the way you write your objects to ObjectOutputStream in the first place:
Directly write the kundenListe and bestellListe objects, so you dont't have to worry about types or number of elements when reading the objects again. Your stream of object then always contains two objects, the two lists.
// use try-with-resources if you're on Java 7 or newer
try (ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(file))) {
// write the complete list of objects
outputStream.writeObject(kundenliste);
outputStream.writeObject(bestellListe);
} catch (IOException e) {
e.printStackTrace(); //TODO proper exception handling
}
Then you could read it just like that:
ArrayList<AKunde> kundenImport = new ArrayList<>();
ArrayList<Bestellung> bestellungenImport = new ArrayList<>();
//again try-with-resources
try (ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file))) {
kundenImport.addAll((List) inputStream.readObject());
bestellungenImport.addAll((List) inputStream.readObject());
} catch (IOException | ClassNotFoundException e) { //multi-catch, if Java 7 or newer
e.printStackTrace(); //TODO proper exception handling
}
Further reads:
The try-with-resources Statement
Catching Multiple Exception Types (...)
I have a program that open 3 streams of data but I do not know how to close all of them, here it is the closing part of the program.
finally {//cerrando muestras
try{
if(muestras!=null){
muestras.close();
}
}catch (IOException e) {
e.printStackTrace();
}finally {//cerrando salida
try{
if(salida!=null){
salida.close();
}
}catch (IOException e){
e.printStackTrace();
}
}
}
I suppose that it is because I can not do that nested finally but I do not know any other method, thank you for your time.
You should use try-with-resource statements introduced in Java 7 rather than closing your streams on your own. Consider the following as an example :
try (
BufferedReader br = new BufferedReader(new FileReader(file));
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(out))
) {
bufferedWriter.write(text);
} catch(IOException e) {
//log or propagate to the caller
}
Observe how you don't have to close the BufferedReader and BufferedWriter streams manually.
If you are using java6 or lower you could use a wrapper for you close().
public void closeStreams(Closeable c){
try{
c.close();
}
catch(IOException e){
}
finally{
// well noting here now..
}
}
And you can use :
finally {//cerrando muestras
if(muestras!=null){
muestras.closeStreams();
}
if(salida!=null){
salida.closeStreams();
}
}
I usually end up making a utility method to do this sort of thing.
Stream muestras;
Stream salida;
...
finally {
closeAll( muestras, salida );
}
public class IoUtils
{
private IoUtils() {}
public static void closeAll( Closeable ... cls ) {
for( Closeable c : cls ) {
if( c != null ) try {
c.close();
} catch( IOException ex ) {
Logger.getLogger( IoUtils.class.getName() ).
log( Level.SEVERE, null, ex );
}
}
}
}
I don't know if my mind just fools me or this is really not working.
I need different type of Logging-classes so I created a abstract-class, the only definition that all classes will have the same is the way the writeToLog is handled:
public abstract class LoggerTemplate {
protected String filename ="log/";
protected File logfile;
protected FileWriter fw;
public void writeToLog(String message) {
if(fw != null) {
try {
message = new SimpleDateFormat("dd-MM-hh:mm").format(new Date()) + " " + message;
fw.write(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
The concrete sub-classes will implement rest of the logic in their constructor, ie one of them:
public class JitterBufferLogger extends LoggerTemplate {
public JitterBufferLogger() {
super();
filename += new SimpleDateFormat("yyyyddMMhhmm'.log'").format(new Date());
if(!new File("log/").exists())
new File("log").mkdir();
logfile = new File(filename);
try {
logfile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
try {
fw = new FileWriter(logfile);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
But when I debug I can see that when calling the writeToLog for a specific logger, it jumps into the LoggerTemplate method, and therefore fw and logfile are null. So it's not working.
Isn't it supposed to work or do I just mess something a bit up and should go into weekend ;-)
It should work, it is normal, that the debugger stepped into the LoggerTemplate class upon entering the writeToLog() method. What is strange that the attributes in the base class have null values.
I have tested your code with the following short test program:
public class Test {
public static void main(String[] args) {
LoggerTemplate lt = new JitterBufferLogger();
lt.writeToLog("Hello");
}
}
After adding fw.flush() to the LoggerTemplate.writeToLog() method just after the fw.write() call, it worked for me, the log file had been created and it contained the log message.
Maybe the new File("log").mkdir() or some other calls throw an exception which you cannot see, because stderr had been redirected somewhere.
So what may be missing?
- filewriter flushing could have helped.
- I can't reproduce the null values with the original code, don't know what happened.
- but as everybody, including me, said: it should work and it does.
Why was nothing in the logfile?
- maybe the flush of fw was missing..
anyhow I wrapped it with a Printwriter:
public abstract class LoggerTemplate {
protected String filename ="log/";
protected File logfile;
protected PrintWriter pw;
public void writeToLog(String message) {
try {
pw = new PrintWriter(new FileWriter(logfile,true));
message = new SimpleDateFormat("dd-MM-hh:mm").format(new Date()) + " " + message + "\n";
pw.write(message);
pw.flush();
pw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
and now it's working like it should and was expected to be.
Note that the fw instantiation in the concrete sub-classes is not needed anymore.