Skip to content
Browse files

making high score code work locally when not online.

  • Loading branch information...
1 parent fbc4a3c commit bd24ba8b3ea25bedf63d63d6ab12f4af0a0f43c5 @aschearer committed Dec 29, 2008
View
0 res/states/highscore/scores.csv
No changes.
View
52 script/batch.php
@@ -0,0 +1,52 @@
+<?php
+/** Read in a CSV file and save the rows. */
+// read CSV data from POST.
+$scores = @$_POST['scores'];
+
+/* Replace this w/ real information. */
+$dsn = 'mysql:dbname=[dbname];host=[dbhost]';
+$user = '[dbuser]';
+$pass = '[dbpass]';
+
+if (!isset($scores)) {
+ echo "failure";
+ exit;
+}
+
+try {
+ $dbh = new PDO($dsn, $user, $pass);
+} catch (PDOException $e) {
+ echo "failure";
+ exit;
+}
+
+// TODO start transaction
+$q = 'INSERT INTO `scores` (clear, name, score) VALUES (?, ?, ?)';
+$stmt = $dbh->prepare($q);
+
+// Get a file pointer to the score data.
+$fp = fopen('php://memory', 'r+');
+fputs($fp, $scores);
+rewind($fp);
+
+try {
+ $dbh->beginTransaction();
+ while (($score = fgetcsv($fp, 1000, ',')) !== FALSE) {
+ $stmt->bindParam(1, $score[2], PDO::PARAM_INT);
+ $stmt->bindParam(2, $score[0], PDO::PARAM_STR);
+ $stmt->bindParam(3, $score[1], PDO::PARAM_INT);
+ if (!$stmt->execute()) {
+ // TODO rollback transaction
+ echo "failure";
+ exit;
+ }
+ }
+ $dbh->commit();
+} catch (Exception $e) {
+ $dbh->rollBack();
+ echo "failure"; // failed somewhere along the way
+ exit;
+}
+
+echo "success";
+?>
View
68 src/com/shade/score/BatchWriter.java
@@ -0,0 +1,68 @@
+package com.shade.score;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+import java.util.Scanner;
+
+import org.newdawn.slick.util.ResourceLoader;
+
+/**
+ * Post all of the scores in a csv file to a server.
+ *
+ * @author Alexander Schearer <aschearer@gmail.com>
+ */
+public class BatchWriter {
+
+ private static final String NEWLINE = "\n";
+ private static final String SERVER = "http://anotherearlymorning.com/games/shade/batch.php";
+
+ private String path;
+
+ public BatchWriter(String path) {
+ this.path = path;
+ }
+
+ public boolean write() {
+ try {
+ String scores = collectScores();
+ String content = "scores=" + URLEncoder.encode(scores, "US-ASCII");
+ URL url = new URL(SERVER);
+ URLConnection c = url.openConnection();
+ c.setConnectTimeout(2000);
+ c.setDoOutput(true);
+ OutputStreamWriter o = new OutputStreamWriter(c.getOutputStream());
+ // write the content
+ o.write(content);
+ o.flush();
+ o.close();
+ // read response and check for success
+ BufferedReader i = new BufferedReader(new InputStreamReader(c
+ .getInputStream()));
+ String response = i.readLine();
+ return response.equals("success");
+
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ private String collectScores() {
+ StringBuilder builder = new StringBuilder();
+ Scanner reader = getScanner();
+ while (reader.hasNextLine()) {
+ builder.append(reader.nextLine());
+ builder.append(NEWLINE);
+ }
+ return builder.toString();
+ }
+
+ private Scanner getScanner() {
+ InputStream stream = ResourceLoader.getResourceAsStream(path);
+ return new Scanner(stream);
+ }
+}
View
30 src/com/shade/score/FailSafeHighScoreReader.java
@@ -0,0 +1,30 @@
+package com.shade.score;
+
+/**
+ * Reader which will fall back to the local high score list if it cannot connect
+ * to the server.
+ *
+ * @author Alexander Schearer <aschearer@gmail.com>
+ */
+public class FailSafeHighScoreReader implements HighScoreReader {
+
+ private static final String FILE = "states/highscore/scores.csv";
+ private static final String SERVER = "http://anotherearlymorning.com/games/shade/board.php";
+
+ private LocalHighScoreReader localReader;
+ private RemoteHighScoreReader remoteReader;
+
+ public FailSafeHighScoreReader() {
+ localReader = new LocalHighScoreReader(FILE);
+ remoteReader = new RemoteHighScoreReader(SERVER);
+ }
+
+ public String[][] getScores(int limit) {
+ String[][] scores = remoteReader.getScores(limit);
+ if (scores == null) {
+ scores = localReader.getScores(limit);
+ }
+ return scores;
+ }
+
+}
View
63 src/com/shade/score/FailSafeHighScoreWriter.java
@@ -0,0 +1,63 @@
+package com.shade.score;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.URL;
+
+import org.newdawn.slick.util.ResourceLoader;
+
+/**
+ * Write high scores to a remote server or locally if you cannot connect to the
+ * server.
+ *
+ * Try to write current score to server
+ * If failed then write locally, exit
+ * If successful try to write each local score to server, continue
+ * If failed then quit, exit
+ * If successful then remove from local file, continue
+ *
+ * @author Alexander Schearer <aschearer@gmail.com>
+ */
+public class FailSafeHighScoreWriter implements HighScoreWriter {
+
+ private static final String FILE = "states/highscore/scores.csv";
+ private static final String SERVER = "http://anotherearlymorning.com/games/shade/post.php";
+
+ private LocalHighScoreWriter localWriter;
+ private RemoteHighScoreWriter remoteWriter;
+ private BatchWriter batchWriter;
+
+ public FailSafeHighScoreWriter() {
+ localWriter = new LocalHighScoreWriter(FILE);
+ remoteWriter = new RemoteHighScoreWriter(SERVER);
+ batchWriter = new BatchWriter(FILE);
+ }
+
+ public boolean write(String name, int score, boolean clear) {
+ // try to write remotely
+ if (remoteWriter.write(name, score, clear)) {
+ // try to write past local scores to server
+ if (batchWriter.write()) {
+ // clear the file
+ clearFile(FILE);
+ }
+ // else do nothing, they will get written later
+ } else {
+ // can't connect to server, write locally
+ return localWriter.write(name, score, clear);
+ }
+ // wrote current score successfully
+ return true;
+ }
+
+ private void clearFile(String f) {
+ try {
+ URL u = ResourceLoader.getResource(f);
+ FileWriter w = new FileWriter(u.getPath());
+ w.flush();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+}
View
4 src/com/shade/score/HighScoreReader.java
@@ -1,7 +1,5 @@
package com.shade.score;
-import org.newdawn.slick.SlickException;
-
public interface HighScoreReader {
/**
@@ -10,5 +8,5 @@
* @param limit Return this many, set to zero to return all.
* @return
*/
- public String[][] getScores(int limit) throws SlickException;
+ public String[][] getScores(int limit);
}
View
4 src/com/shade/score/HighScoreWriter.java
@@ -1,8 +1,6 @@
package com.shade.score;
-import org.newdawn.slick.SlickException;
-
public interface HighScoreWriter {
- public boolean write(String name, int score, boolean clear) throws SlickException;
+ public boolean write(String name, int score, boolean clear);
}
View
76 src/com/shade/score/LocalHighScoreReader.java
@@ -0,0 +1,76 @@
+package com.shade.score;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+
+import org.newdawn.slick.util.ResourceLoader;
+
+import com.shade.util.CsvReader;
+
+/**
+ * Read high scores from a csv file located in the jar.
+ *
+ * This is useful if the remote server cannot be reached.
+ *
+ * @author Alexander Schearer <aschearer@gmail.com>
+ */
+public class LocalHighScoreReader implements HighScoreReader {
+
+ private static final int NAME = 0;
+ private static final int SCORE = 1;
+ private static final int CLEAR = 2;
+
+ private CsvReader reader;
+
+ public LocalHighScoreReader(String path) {
+ InputStream stream = ResourceLoader.getResourceAsStream(path);
+ InputStreamReader input = new InputStreamReader(stream);
+ reader = new CsvReader(input);
+ }
+
+ /**
+ * Returns all the scores if zero is passed.
+ */
+ public String[][] getScores(int limit) {
+ LinkedList<String[]> rows = new LinkedList<String[]>();
+ try {
+ while (reader.readRecord()) {
+ String[] row = new String[3];
+ row[NAME] = reader.get(NAME);
+ row[SCORE] = reader.get(SCORE);
+ row[CLEAR] = reader.get(CLEAR);
+ rows.add(row);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ Collections.sort(rows, new Comparator<String[]>() {
+
+ public int compare(String[] s1, String[] s2) {
+ return s1[SCORE].compareTo(s2[SCORE]);
+ }
+
+ });
+
+ return firstN(rows, limit);
+ }
+
+ private String[][] firstN(LinkedList<String[]> rows, int limit) {
+ limit = (limit == 0) ? rows.size() : limit;
+ int size = (rows.size() > limit) ? limit : rows.size();
+ String[][] n = new String[size][3];
+
+ for (int i = 0; i < size; i++) {
+ n[i][NAME] = rows.get(i)[NAME];
+ n[i][SCORE] = rows.get(i)[SCORE];
+ n[i][CLEAR] = rows.get(i)[CLEAR];
+ }
+ return n;
+ }
+
+}
View
49 src/com/shade/score/LocalHighScoreWriter.java
@@ -0,0 +1,49 @@
+package com.shade.score;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.URL;
+
+import org.newdawn.slick.util.ResourceLoader;
+
+import com.shade.util.CsvWriter;
+
+public class LocalHighScoreWriter implements HighScoreWriter {
+
+ private static final int NAME = 0;
+ private static final int SCORE = 1;
+ private static final int CLEAR = 2;
+
+ private static final char COMMA = ',';
+ private CsvWriter writer;
+
+ public LocalHighScoreWriter(String path) {
+ try {
+ URL url = ResourceLoader.getResource(path);
+ FileWriter stream = new FileWriter(url.getPath(), true);
+ writer = new CsvWriter(stream, COMMA);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public boolean write(String name, int score, boolean clear) {
+ String[] row = new String[3];
+ row[NAME] = name;
+ row[SCORE] = score + "";
+ row[CLEAR] = (clear) ? "1" : "0";
+ return write(row[NAME], row[SCORE], row[CLEAR]);
+ }
+
+ protected boolean write(String name, String score, String clear) {
+ String[] row = new String[] { name, score, clear };
+ try {
+ writer.writeRecord(row);
+ writer.flush();
+ } catch (IOException e) {
+ return false;
+ }
+ return true;
+ }
+
+}
View
14 src/com/shade/score/RemoteHighScoreReader.java
@@ -6,10 +6,16 @@
import java.net.URLEncoder;
import java.util.LinkedList;
-import org.newdawn.slick.SlickException;
-
import com.shade.util.CsvReader;
+/**
+ * Read high scores from a remote server.
+ *
+ * This performs a get request to retrieve a list of high scores from the
+ * server. It expects the high scores to be in CSV format.
+ *
+ * @author Alexander Schearer <aschearer@gmail.com>
+ */
public class RemoteHighScoreReader implements HighScoreReader {
private String base;
@@ -18,10 +24,10 @@ public RemoteHighScoreReader(String path) {
base = path;
}
- public String[][] getScores(int limit) throws SlickException {
+ public String[][] getScores(int limit) {
try {
String target = base + "?num_scores="
- + URLEncoder.encode("" + limit, "UTF-8");
+ + URLEncoder.encode("" + limit, "US-ASCII");
// open connection to read
URL url = new URL(target);
View
16 src/com/shade/score/RemoteHighScoreWriter.java
@@ -7,22 +7,24 @@
import java.net.URLConnection;
import java.net.URLEncoder;
-import org.newdawn.slick.SlickException;
-
public class RemoteHighScoreWriter implements HighScoreWriter {
private String base;
- public RemoteHighScoreWriter(String path) throws SlickException {
+ public RemoteHighScoreWriter(String path) {
base = path;
}
- public boolean write(String name, int score, boolean clear) throws SlickException {
+ public boolean write(String name, int score, boolean clear) {
+ String cleared = (clear) ? "1" : "0";
+ return write(name, score + "", cleared);
+ }
+
+ protected boolean write(String name, String score, String clear) {
try {
- String content = "name=" + URLEncoder.encode(name, "UTF-8");
+ String content = "name=" + URLEncoder.encode(name, "US-ASCII");
content += "&score=" + score;
- content += "&clear=";
- content += (clear) ? 1 : 0;
+ content += "&clear=" + clear;
URL url = new URL(base);
URLConnection c = url.openConnection();
c.setConnectTimeout(2000);
View
43 src/com/shade/states/EnterScoreState.java
@@ -14,8 +14,8 @@
import com.shade.controls.ClickListener;
import com.shade.controls.SlickButton;
import com.shade.util.ResourceManager;
+import com.shade.score.FailSafeHighScoreWriter;
import com.shade.score.HighScoreWriter;
-import com.shade.score.RemoteHighScoreWriter;
public class EnterScoreState extends BasicGameState {
@@ -46,8 +46,7 @@ public EnterScoreState(MasterState m) throws SlickException {
resource.register("playagain-down", "states/enter/playagain-down.png");
resource.register("losers-wreath", "states/enter/losers-wreath.png");
resource.register("winners-wreath", "states/enter/winners-wreath.png");
- writer = new RemoteHighScoreWriter(
- "http://www.anotherearlymorning.com/games/shade/post.php");
+ writer = new FailSafeHighScoreWriter();
// badEnding = new Music("states/enter/loser.ogg", true);
// goodEnding = new Music("states/enter/winner.ogg", true);
}
@@ -144,28 +143,24 @@ private void initTextField(GameContainer container) throws SlickException {
input.addListener(new ComponentListener() {
public void componentActivated(AbstractComponent c) {
- try {
- int numTries = 3;
- boolean written = false;
- while (!written && numTries > 0) {
- written = writer.write(input.getText(),
- master.scorecard.read(), master.scorecard
- .isCleared());
- numTries--;
- }
- input.setAcceptingInput(false);
- completed = true;
- // TODO really tell the user this or just silently fail?
- // if (!written) {
- // message = "You're not online so we couldn't record this
- // Ÿber score.";
- // } else {
- message = "Way to go " + input.getText() + "!! ... "
- + randomResponse();
- // }
- } catch (SlickException e) {
- e.printStackTrace();
+ int numTries = 3;
+ boolean written = false;
+ while (!written && numTries > 0) {
+ written = writer.write(input.getText(),
+ master.scorecard.read(), master.scorecard
+ .isCleared());
+ numTries--;
}
+ input.setAcceptingInput(false);
+ completed = true;
+ // TODO really tell the user this or just silently fail?
+ // if (!written) {
+ // message = "You're not online so we couldn't record this
+ // Ÿber score.";
+ // } else {
+ message = "Way to go " + input.getText() + "!! ... "
+ + randomResponse();
+ // }
}
});
View
4 src/com/shade/states/HighscoreState.java
@@ -14,8 +14,8 @@
import com.shade.controls.FadeInText;
import com.shade.controls.SlickButton;
import com.shade.util.ResourceManager;
+import com.shade.score.FailSafeHighScoreReader;
import com.shade.score.HighScoreReader;
-import com.shade.score.RemoteHighScoreReader;
public class HighscoreState extends BasicGameState {
@@ -42,7 +42,7 @@ public HighscoreState(MasterState m) throws SlickException {
scores = new ArrayList<FadeInText>();
crowns = new ArrayList<FadeInImage>();
- reader = new RemoteHighScoreReader("http://anotherearlymorning.com/games/shade/board.php");
+ reader = new FailSafeHighScoreReader();
}
@Override
View
1 src/com/shade/states/InGameState.java
@@ -196,6 +196,7 @@ private void fadeCentered(GameContainer c, String s) {
private void exit(StateBasedGame game, int state) {
master.control.flushControls();
master.control.killPlayer();
+ master.music.fade(2000, 1f, false);
game.enterState(state);
}
View
599 src/com/shade/util/CsvWriter.java
@@ -0,0 +1,599 @@
+/*
+ * Java CSV is a stream based library for reading and writing
+ * CSV and other delimited data.
+ *
+ * Copyright (C) Bruce Dunwiddie bruce@csvreader.com
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+package com.shade.util;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.nio.charset.Charset;
+
+/**
+ * A stream based writer for writing delimited text data to a file or a stream.
+ */
+public class CsvWriter {
+ private PrintWriter outputStream = null;
+
+ private String fileName = null;
+
+ private boolean firstColumn = true;
+
+ private boolean useCustomRecordDelimiter = false;
+
+ private Charset charset = null;
+
+ // this holds all the values for switches that the user is allowed to set
+ private UserSettings userSettings = new UserSettings();
+
+ private boolean initialized = false;
+
+ private boolean closed = false;
+
+ /**
+ * Double up the text qualifier to represent an occurance of the text
+ * qualifier.
+ */
+ public static final int ESCAPE_MODE_DOUBLED = 1;
+
+ /**
+ * Use a backslash character before the text qualifier to represent an
+ * occurance of the text qualifier.
+ */
+ public static final int ESCAPE_MODE_BACKSLASH = 2;
+
+ /**
+ * Creates a {@link com.csvreader.CsvWriter CsvWriter} object using a file
+ * as the data destination.
+ *
+ * @param fileName
+ * The path to the file to output the data.
+ * @param delimiter
+ * The character to use as the column delimiter.
+ * @param charset
+ * The {@link java.nio.charset.Charset Charset} to use while
+ * writing the data.
+ */
+ public CsvWriter(String fileName, char delimiter, Charset charset) {
+ if (fileName == null) {
+ throw new IllegalArgumentException("Parameter fileName can not be null.");
+ }
+
+ if (charset == null) {
+ throw new IllegalArgumentException("Parameter charset can not be null.");
+ }
+
+ this.fileName = fileName;
+ userSettings.Delimiter = delimiter;
+ this.charset = charset;
+ }
+
+ /**
+ * Creates a {@link com.csvreader.CsvWriter CsvWriter} object using a file
+ * as the data destination.&nbsp;Uses a comma as the column delimiter and
+ * ISO-8859-1 as the {@link java.nio.charset.Charset Charset}.
+ *
+ * @param fileName
+ * The path to the file to output the data.
+ */
+ public CsvWriter(String fileName) {
+ this(fileName, Letters.COMMA, Charset.forName("ISO-8859-1"));
+ }
+
+ /**
+ * Creates a {@link com.csvreader.CsvWriter CsvWriter} object using a Writer
+ * to write data to.
+ *
+ * @param outputStream
+ * The stream to write the column delimited data to.
+ * @param delimiter
+ * The character to use as the column delimiter.
+ */
+ public CsvWriter(Writer outputStream, char delimiter) {
+ if (outputStream == null) {
+ throw new IllegalArgumentException("Parameter outputStream can not be null.");
+ }
+
+ this.outputStream = new PrintWriter(outputStream);
+ userSettings.Delimiter = delimiter;
+ initialized = true;
+ }
+
+ /**
+ * Creates a {@link com.csvreader.CsvWriter CsvWriter} object using an
+ * OutputStream to write data to.
+ *
+ * @param outputStream
+ * The stream to write the column delimited data to.
+ * @param delimiter
+ * The character to use as the column delimiter.
+ * @param charset
+ * The {@link java.nio.charset.Charset Charset} to use while
+ * writing the data.
+ */
+ public CsvWriter(OutputStream outputStream, char delimiter, Charset charset) {
+ this(new OutputStreamWriter(outputStream, charset), delimiter);
+ }
+
+ /**
+ * Gets the character being used as the column delimiter.
+ *
+ * @return The character being used as the column delimiter.
+ */
+ public char getDelimiter() {
+ return userSettings.Delimiter;
+ }
+
+ /**
+ * Sets the character to use as the column delimiter.
+ *
+ * @param delimiter
+ * The character to use as the column delimiter.
+ */
+ public void setDelimiter(char delimiter) {
+ userSettings.Delimiter = delimiter;
+ }
+
+ public char getRecordDelimiter() {
+ return userSettings.RecordDelimiter;
+ }
+
+ /**
+ * Sets the character to use as the record delimiter.
+ *
+ * @param recordDelimiter
+ * The character to use as the record delimiter. Default is
+ * combination of standard end of line characters for Windows,
+ * Unix, or Mac.
+ */
+ public void setRecordDelimiter(char recordDelimiter) {
+ useCustomRecordDelimiter = true;
+ userSettings.RecordDelimiter = recordDelimiter;
+ }
+
+ /**
+ * Gets the character to use as a text qualifier in the data.
+ *
+ * @return The character to use as a text qualifier in the data.
+ */
+ public char getTextQualifier() {
+ return userSettings.TextQualifier;
+ }
+
+ /**
+ * Sets the character to use as a text qualifier in the data.
+ *
+ * @param textQualifier
+ * The character to use as a text qualifier in the data.
+ */
+ public void setTextQualifier(char textQualifier) {
+ userSettings.TextQualifier = textQualifier;
+ }
+
+ /**
+ * Whether text qualifiers will be used while writing data or not.
+ *
+ * @return Whether text qualifiers will be used while writing data or not.
+ */
+ public boolean getUseTextQualifier() {
+ return userSettings.UseTextQualifier;
+ }
+
+ /**
+ * Sets whether text qualifiers will be used while writing data or not.
+ *
+ * @param useTextQualifier
+ * Whether to use a text qualifier while writing data or not.
+ */
+ public void setUseTextQualifier(boolean useTextQualifier) {
+ userSettings.UseTextQualifier = useTextQualifier;
+ }
+
+ public int getEscapeMode() {
+ return userSettings.EscapeMode;
+ }
+
+ public void setEscapeMode(int escapeMode) {
+ userSettings.EscapeMode = escapeMode;
+ }
+
+ public void setComment(char comment) {
+ userSettings.Comment = comment;
+ }
+
+ public char getComment() {
+ return userSettings.Comment;
+ }
+
+ /**
+ * Whether fields will be surrounded by the text qualifier even if the
+ * qualifier is not necessarily needed to escape this field.
+ *
+ * @return Whether fields will be forced to be qualified or not.
+ */
+ public boolean getForceQualifier() {
+ return userSettings.ForceQualifier;
+ }
+
+ /**
+ * Use this to force all fields to be surrounded by the text qualifier even
+ * if the qualifier is not necessarily needed to escape this field. Default
+ * is false.
+ *
+ * @param forceQualifier
+ * Whether to force the fields to be qualified or not.
+ */
+ public void setForceQualifier(boolean forceQualifier) {
+ userSettings.ForceQualifier = forceQualifier;
+ }
+
+ /**
+ * Writes another column of data to this record.
+ *
+ * @param content
+ * The data for the new column.
+ * @param preserveSpaces
+ * Whether to preserve leading and trailing whitespace in this
+ * column of data.
+ * @exception IOException
+ * Thrown if an error occurs while writing data to the
+ * destination stream.
+ */
+ public void write(String content, boolean preserveSpaces)
+ throws IOException {
+ checkClosed();
+
+ checkInit();
+
+ if (content == null) {
+ content = "";
+ }
+
+ if (!firstColumn) {
+ outputStream.write(userSettings.Delimiter);
+ }
+
+ boolean textQualify = userSettings.ForceQualifier;
+
+ if (!preserveSpaces && content.length() > 0) {
+ content = content.trim();
+ }
+
+ if (!textQualify
+ && userSettings.UseTextQualifier
+ && (content.indexOf(userSettings.TextQualifier) > -1
+ || content.indexOf(userSettings.Delimiter) > -1
+ || (!useCustomRecordDelimiter && (content
+ .indexOf(Letters.LF) > -1 || content
+ .indexOf(Letters.CR) > -1))
+ || (useCustomRecordDelimiter && content
+ .indexOf(userSettings.RecordDelimiter) > -1)
+ || (firstColumn && content.length() > 0 && content
+ .charAt(0) == userSettings.Comment) ||
+ // check for empty first column, which if on its own line must
+ // be qualified or the line will be skipped
+ (firstColumn && content.length() == 0))) {
+ textQualify = true;
+ }
+
+ if (userSettings.UseTextQualifier && !textQualify
+ && content.length() > 0 && preserveSpaces) {
+ char firstLetter = content.charAt(0);
+
+ if (firstLetter == Letters.SPACE || firstLetter == Letters.TAB) {
+ textQualify = true;
+ }
+
+ if (!textQualify && content.length() > 1) {
+ char lastLetter = content.charAt(content.length() - 1);
+
+ if (lastLetter == Letters.SPACE || lastLetter == Letters.TAB) {
+ textQualify = true;
+ }
+ }
+ }
+
+ if (textQualify) {
+ outputStream.write(userSettings.TextQualifier);
+
+ if (userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH) {
+ content = replace(content, "" + Letters.BACKSLASH, ""
+ + Letters.BACKSLASH + Letters.BACKSLASH);
+ content = replace(content, "" + userSettings.TextQualifier, ""
+ + Letters.BACKSLASH + userSettings.TextQualifier);
+ } else {
+ content = replace(content, "" + userSettings.TextQualifier, ""
+ + userSettings.TextQualifier
+ + userSettings.TextQualifier);
+ }
+ } else if (userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH) {
+ content = replace(content, "" + Letters.BACKSLASH, ""
+ + Letters.BACKSLASH + Letters.BACKSLASH);
+ content = replace(content, "" + userSettings.Delimiter, ""
+ + Letters.BACKSLASH + userSettings.Delimiter);
+
+ if (useCustomRecordDelimiter) {
+ content = replace(content, "" + userSettings.RecordDelimiter,
+ "" + Letters.BACKSLASH + userSettings.RecordDelimiter);
+ } else {
+ content = replace(content, "" + Letters.CR, ""
+ + Letters.BACKSLASH + Letters.CR);
+ content = replace(content, "" + Letters.LF, ""
+ + Letters.BACKSLASH + Letters.LF);
+ }
+
+ if (firstColumn && content.length() > 0
+ && content.charAt(0) == userSettings.Comment) {
+ if (content.length() > 1) {
+ content = "" + Letters.BACKSLASH + userSettings.Comment
+ + content.substring(1);
+ } else {
+ content = "" + Letters.BACKSLASH + userSettings.Comment;
+ }
+ }
+ }
+
+ outputStream.write(content);
+
+ if (textQualify) {
+ outputStream.write(userSettings.TextQualifier);
+ }
+
+ firstColumn = false;
+ }
+
+ /**
+ * Writes another column of data to this record.&nbsp;Does not preserve
+ * leading and trailing whitespace in this column of data.
+ *
+ * @param content
+ * The data for the new column.
+ * @exception IOException
+ * Thrown if an error occurs while writing data to the
+ * destination stream.
+ */
+ public void write(String content) throws IOException {
+ write(content, false);
+ }
+
+ public void writeComment(String commentText) throws IOException {
+ checkClosed();
+
+ checkInit();
+
+ outputStream.write(userSettings.Comment);
+
+ outputStream.write(commentText);
+
+ if (useCustomRecordDelimiter) {
+ outputStream.write(userSettings.RecordDelimiter);
+ } else {
+ outputStream.println();
+ }
+
+ firstColumn = true;
+ }
+
+ /**
+ * Writes a new record using the passed in array of values.
+ *
+ * @param values
+ * Values to be written.
+ *
+ * @param preserveSpaces
+ * Whether to preserver leading and trailing spaces in columns
+ * while writing out to the record or not.
+ *
+ * @throws IOException
+ * Thrown if an error occurs while writing data to the
+ * destination stream.
+ */
+ public void writeRecord(String[] values, boolean preserveSpaces)
+ throws IOException {
+ if (values != null && values.length > 0) {
+ for (int i = 0; i < values.length; i++) {
+ write(values[i], preserveSpaces);
+ }
+
+ endRecord();
+ }
+ }
+
+ /**
+ * Writes a new record using the passed in array of values.
+ *
+ * @param values
+ * Values to be written.
+ *
+ * @throws IOException
+ * Thrown if an error occurs while writing data to the
+ * destination stream.
+ */
+ public void writeRecord(String[] values) throws IOException {
+ writeRecord(values, false);
+ }
+
+ /**
+ * Ends the current record by sending the record delimiter.
+ *
+ * @exception IOException
+ * Thrown if an error occurs while writing data to the
+ * destination stream.
+ */
+ public void endRecord() throws IOException {
+ checkClosed();
+
+ checkInit();
+
+ if (useCustomRecordDelimiter) {
+ outputStream.write(userSettings.RecordDelimiter);
+ } else {
+ outputStream.println();
+ }
+
+ firstColumn = true;
+ }
+
+ /**
+ *
+ */
+ private void checkInit() throws IOException {
+ if (!initialized) {
+ if (fileName != null) {
+ outputStream = new PrintWriter(new OutputStreamWriter(
+ new FileOutputStream(fileName), charset));
+ }
+
+ initialized = true;
+ }
+ }
+
+ /**
+ * Clears all buffers for the current writer and causes any buffered data to
+ * be written to the underlying device.
+ */
+ public void flush() {
+ outputStream.flush();
+ }
+
+ /**
+ * Closes and releases all related resources.
+ */
+ public void close() {
+ if (!closed) {
+ close(true);
+
+ closed = true;
+ }
+ }
+
+ /**
+ *
+ */
+ private void close(boolean closing) {
+ if (!closed) {
+ if (closing) {
+ charset = null;
+ }
+
+ try {
+ if (initialized) {
+ outputStream.close();
+ }
+ } catch (Exception e) {
+ // just eat the exception
+ }
+
+ outputStream = null;
+
+ closed = true;
+ }
+ }
+
+ /**
+ *
+ */
+ private void checkClosed() throws IOException {
+ if (closed) {
+ throw new IOException(
+ "This instance of the CsvWriter class has already been closed.");
+ }
+ }
+
+ /**
+ *
+ */
+ protected void finalize() {
+ close(false);
+ }
+
+ private class Letters {
+ public static final char LF = '\n';
+
+ public static final char CR = '\r';
+
+ public static final char QUOTE = '"';
+
+ public static final char COMMA = ',';
+
+ public static final char SPACE = ' ';
+
+ public static final char TAB = '\t';
+
+ public static final char POUND = '#';
+
+ public static final char BACKSLASH = '\\';
+
+ public static final char NULL = '\0';
+ }
+
+ private class UserSettings {
+ // having these as publicly accessible members will prevent
+ // the overhead of the method call that exists on properties
+ public char TextQualifier;
+
+ public boolean UseTextQualifier;
+
+ public char Delimiter;
+
+ public char RecordDelimiter;
+
+ public char Comment;
+
+ public int EscapeMode;
+
+ public boolean ForceQualifier;
+
+ public UserSettings() {
+ TextQualifier = Letters.QUOTE;
+ UseTextQualifier = true;
+ Delimiter = Letters.COMMA;
+ RecordDelimiter = Letters.NULL;
+ Comment = Letters.POUND;
+ EscapeMode = ESCAPE_MODE_DOUBLED;
+ ForceQualifier = false;
+ }
+ }
+
+ public static String replace(String original, String pattern, String replace) {
+ final int len = pattern.length();
+ int found = original.indexOf(pattern);
+
+ if (found > -1) {
+ StringBuffer sb = new StringBuffer();
+ int start = 0;
+
+ while (found != -1) {
+ sb.append(original.substring(start, found));
+ sb.append(replace);
+ start = found + len;
+ found = original.indexOf(pattern, start);
+ }
+
+ sb.append(original.substring(start));
+
+ return sb.toString();
+ } else {
+ return original;
+ }
+ }
+}

0 comments on commit bd24ba8

Please sign in to comment.
Something went wrong with that request. Please try again.