I tried to create an SQLite database and do some stuff with it. But I found that my onCreate method is not even invoked!!
I am sending a message to LogCat on the begining of the onCreate method.
My assumption is, the (super) constructor will invoke onCreate method. Is that right?
My Code:
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteDatabase;
import android.content.Context;
import android.database.Cursor;
import android.content.ContentValues;
import android.util.Log;
public class DatabaseHandler extends SQLiteOpenHelper {
// Static Constants
/*** Database details ***/
// Database version
private static final int DATABASE_VERSION = 1;
// Database name
private static final String DATABASE_NAME = "database_name";
/*** Database Tables ***/
/** Events **/
// Event table
private static final String TABLE_EVENT = "event";
// Event table columns
private static final String COLUMN_EVENT_EID = "_eid";
private static final String COLUMN_EVENT_CREATION_DATE = "creation_date";
private static final String COLUMN_EVENT_TITLE = "title";
private static final String COLUMN_EVENT_ICON = "icon";
public DatabaseHandler(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
#Override
public void onCreate(SQLiteDatabase db) {
Log.e("MyApp", "onCreate invoked");
// Tables creation queries
String CREATE_EVENT_TABLE = "create table " + TABLE_EVENT + "(" + COLUMN_EVENT_EID + " integer primary key, "
+ COLUMN_EVENT_CREATION_DATE + " text, "
+ COLUMN_EVENT_TITLE + " text, "
+ COLUMN_EVENT_ICON + " text)";
// Creating tables
db.execSQL(CREATE_EVENT_TABLE);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.e("MyApp", "onUpgrade invoked");
db.execSQL("DROP TABLE IF EXISTS " + TABLE_EVENT);
}
}
MainActivity Code:
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DatabaseHandler db = new DatabaseHandler(this);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
The documentation says:
The database is not actually created or opened until one of getWritableDatabase() or getReadableDatabase() is called.
Let me clear the on the logic flow. Here is Lazy-initialization concept.
The (super) constructor on DatabaseHandler will not invoke onCreate method. Calling DatabaseHandler constructor will initializes: context, database name, factory that creates the database, database version, and database error handler.
getWritableDatabase() > getDatabaseLocked() > - SQLiteDatabase.create()
OR
getReadableDatabase() > getDatabaseLocked() > - SQLiteDatabase.create()
Answer: After your database gets created successfully, your configurations changes, next time again getReadableDatabase() or getWritableDatabase() calls getDatabaseLocked() and there onCreate(db) method inside getDatabaseLocked() gets executed.
Explanation:
The above SQLiteDatabase.create() method is responsible to create SQLiteDatabase in the disk.
But the process in lazy-initialization (mean, it doesn't make everything ready. It creates those objects on the runtime if you need them. For this it used a lot of if..else statements).
If you see the full body of getDatabaseLocked(), this is below. [You can search onCreate() method inside the body of getDatabaseLocked()]
private SQLiteDatabase getDatabaseLocked(boolean writable) {
if (mDatabase != null) {
if (!mDatabase.isOpen()) {
// Darn! The user closed the database by calling mDatabase.close().
mDatabase = null;
} else if (!writable || !mDatabase.isReadOnly()) {
// The database is already open for business.
return mDatabase;
}
}
if (mIsInitializing) {
throw new IllegalStateException("getDatabase called recursively");
}
SQLiteDatabase db = mDatabase;
try {
mIsInitializing = true;
if (db != null) {
if (writable && db.isReadOnly()) {
db.reopenReadWrite();
}
} else if (mName == null) {
db = SQLiteDatabase.create(null);
} else {
try {
if (DEBUG_STRICT_READONLY && !writable) {
final String path = mContext.getDatabasePath(mName).getPath();
db = SQLiteDatabase.openDatabase(path, mFactory,
SQLiteDatabase.OPEN_READONLY, mErrorHandler);
} else {
db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ?
Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 0,
mFactory, mErrorHandler);
}
} catch (SQLiteException ex) {
if (writable) {
throw ex;
}
Log.e(TAG, "Couldn't open " + mName
+ " for writing (will try read-only):", ex);
final String path = mContext.getDatabasePath(mName).getPath();
db = SQLiteDatabase.openDatabase(path, mFactory,
SQLiteDatabase.OPEN_READONLY, mErrorHandler);
}
}
onConfigure(db);
final int version = db.getVersion();
if (version != mNewVersion) {
if (db.isReadOnly()) {
throw new SQLiteException("Can't upgrade read-only database from version " +
db.getVersion() + " to " + mNewVersion + ": " + mName);
}
db.beginTransaction();
try {
if (version == 0) {
onCreate(db);
} else {
if (version > mNewVersion) {
onDowngrade(db, version, mNewVersion);
} else {
onUpgrade(db, version, mNewVersion);
}
}
db.setVersion(mNewVersion);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
onOpen(db);
if (db.isReadOnly()) {
Log.w(TAG, "Opened " + mName + " in read-only mode");
}
mDatabase = db;
return db;
} finally {
mIsInitializing = false;
if (db != null && db != mDatabase) {
db.close();
}
}
}
Please note, inside the body of getDatabaseLocked() method, there are so many if.. else cases. These if.. else cases determines your current environment (configuration), and based on your current environment they call appropriate methods to initialize/configure whatever needed.
Also, note: All the callbacks methods in your DatabaseHandler (class that implemented SQLiteOpenHelper) are called inside the getDatabaseLocked() body.
Source code SQLiteOpenHelper.java:
https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/database/sqlite/SQLiteOpenHelper.java
Source code SQLiteDatabase.java:
https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/database/sqlite/SQLiteDatabase.java
Sample to follow: https://github.com/uddhavgautam/SQLiteBasicSample
Your are right, the (super) constructor will invoke onCreate method, BUT only if the actual database does not exits.
From http://developer.android.com/reference/android/database/sqlite/SQLiteOpenHelper.html#onCreate%28android.database.sqlite.SQLiteDatabase%29
A helper class to manage database creation and version management.
You create a subclass implementing onCreate(SQLiteDatabase),
onUpgrade(SQLiteDatabase, int, int) and optionally
onOpen(SQLiteDatabase), and this class takes care of opening the
database if it exists, creating it if it does not, and upgrading it as
necessary.
As the official documents says, "getWritableDatabase () Create and/or open a database that will be used for reading and writing. The first time this is called, the database will be opened and onCreate(SQLiteDatabase), onUpgrade(SQLiteDatabase, int, int) and/or onOpen(SQLiteDatabase) will be called."
Once opened successfully, the database is cached, so you can call this method every time you need to write to the database. (Make sure to call close() when you no longer need the database.) Errors such as bad permissions or a full disk may cause this method to fail, but future attempts may succeed if the problem is fixed.
http://developer.android.com/reference/android/database/sqlite/SQLiteOpenHelper.html#getWritableDatabase()
Related
I have a use case where the records are to be persisted in table which has foriegn key to itself.
Example:
zObject
{
uid,
name,
parentuid
}
parent uid also present in same table and any object which has non existent parentuid will be failed to persist .
At times the records are placed in the topic such a way that the dependency is not at the head of the list , instead it will be after the dependent records are present
This will cause failure in process the record . I have used the seektocurrenterrorhandler which actually retries the same failed records for the given backoff and it fails since the dependency is not met .
Is there any way where I can requeue the record at the end of the topic so that dependency is met ? If it fails for day 5 times even after enqueue , the records can be pushed to a DLT .
Thanks,
Rajasekhar
There is nothing built in; you can, however, use a custom destination resolver in the DeadLetterPublishingRecoverer to determine which topic to publish to, based on a header in the failed record.
See https://docs.spring.io/spring-kafka/docs/2.6.2/reference/html/#dead-letters
EDIT
#SpringBootApplication
public class So64646996Application {
public static void main(String[] args) {
SpringApplication.run(So64646996Application.class, args);
}
#Bean
public NewTopic topic() {
return TopicBuilder.name("so64646996").partitions(1).replicas(1).build();
}
#Bean
public NewTopic dlt() {
return TopicBuilder.name("so64646996.DLT").partitions(1).replicas(1).build();
}
#Bean
public ErrorHandler eh(KafkaOperations<String, String> template) {
return new SeekToCurrentErrorHandler(new DeadLetterPublishingRecoverer(template,
(rec, ex) -> {
org.apache.kafka.common.header.Header retries = rec.headers().lastHeader("retries");
if (retries == null) {
retries = new RecordHeader("retries", new byte[] { 1 });
rec.headers().add(retries);
}
else {
retries.value()[0]++;
}
return retries.value()[0] > 5
? new TopicPartition("so64646996.DLT", rec.partition())
: new TopicPartition("so64646996", rec.partition());
}), new FixedBackOff(0L, 0L));
}
#KafkaListener(id = "so64646996", topics = "so64646996")
public void listen(String in,
#Header(KafkaHeaders.OFFSET) long offset,
#Header(name = "retries", required = false) byte[] retry) {
System.out.println(in + "#" + offset + ":" + retry[0]);
throw new IllegalStateException();
}
#KafkaListener(id = "so64646996.DLT", topics = "so64646996.DLT")
public void listenDLT(String in,
#Header(KafkaHeaders.OFFSET) long offset,
#Header(name = "retries", required = false) byte[] retry) {
System.out.println("DLT: " + in + "#" + offset + ":" + retry[0]);
}
#Bean
public ApplicationRunner runner(KafkaTemplate<String, String> template) {
return args -> System.out.println(template.send("so64646996", "foo").get(10, TimeUnit.SECONDS)
.getRecordMetadata());
}
}
there is a table with 3 columns: id, title, poem, favorite.
So, the favorite is written 1 or 0, the default is 0, it changes if the user adds a line to the Favorites.
The problem is that if I update the database (add more rows), all Favorites will disappear from the user. How to transfer the Favorites from the old database to the new one in the onUpgrade method?
DbHelper Class Code
public class PoemsDbHelper extends SQLiteOpenHelper {
private static String DB_NAME = "brodsky.db";
private static String DB_PATH = "";
private static final int DB_VERSION = 3;
private SQLiteDatabase db;
private final Context context;
private boolean needUpdate = false;
public PoemsDbHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
if (android.os.Build.VERSION.SDK_INT >= 17)
DB_PATH = context.getApplicationInfo().dataDir + "/databases/";
else
DB_PATH = "/data/data/" + context.getPackageName() + "/databases/";
this.context = context;
copyDataBase();
this.getReadableDatabase();
}
public void updateDataBase() throws IOException {
if (needUpdate) {
File dbFile = new File(DB_PATH + DB_NAME);
if (dbFile.exists())
dbFile.delete();
copyDataBase();
needUpdate = false;
}
}
private boolean checkDataBase() {
File dbFile = new File(DB_PATH + DB_NAME);
return dbFile.exists();
}
private void copyDataBase() {
if (!checkDataBase()) {
this.getReadableDatabase();
this.close();
try {
copyDBFile();
} catch (IOException mIOException) {
throw new Error("ErrorCopyingDataBase");
}
}
}
private void copyDBFile() throws IOException {
InputStream input = context.getAssets().open(DB_NAME);
//InputStream input = context.getResources().openRawResource(R.raw.info);
OutputStream output = new FileOutputStream(DB_PATH + DB_NAME);
byte[] buffer = new byte[1024];
int length;
while ((length = input.read(buffer)) > 0)
output.write(buffer, 0, length);
output.flush();
output.close();
input.close();
}
public boolean openDataBase() throws SQLException {
db = SQLiteDatabase.openDatabase(DB_PATH + DB_NAME, null, SQLiteDatabase.CREATE_IF_NECESSARY);
return db != null;
}
#Override
public synchronized void close() {
if (db != null)
db.close();
super.close();
}
#Override
public void onCreate(SQLiteDatabase db) {
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (newVersion > oldVersion)
needUpdate = true;
}
}
In this case it seems you are overwriting your "old" database with a new, clean, empty database. I suggest trying copying the old database file to a temporary backup, overwrite old database file with new, clean, empty database file.
Then you must define an upgrade strategy, i.e. how are fields moved from v. 1 to v. 2. What happens if you removed a field? What happens if you added a new field? Has some of your data been restructured? This is an exercise that is best sketched out on paper.
You can now attach the old, backed up database to the new, clean database (see SQL command ATTACH), and insert new records into the new database from the old, following the strategy you sketched out above.
If anything fails, you can always revert to your backup file and notify the user, instead of loosing all their data.
Im facing a potential chase error. Im using javafx TableView to display my data, and I'm periodically receiving an update request externally which calls my update function. I also have some listeners which does stuff such as handle mousedrag events etc. What i want to do is to do something like this:
private void handleEvent(){
TableView.setRowFactory(new Callback<TableView<MyModel>, TableRow<MyModel>>(){
public TableRow<MyModel> call(TableView<MyModel> p) {
final TableRow row = new TableRow();
row.setOnDragDetected(new EventHandler<MouseEvent>(){
public void handle(){
//implement some kind of lock to prevent receiving data update
}
}
row.setOnMouseDragExited(new EventHandler<MouseDragEvent>(){
//release lock to accept update
}
}
}
//this method is being called externally periodically
public void updateModel(MyModel model){
//this won't work because it will skip entirely if it's locked,
//I want it to instead run later when lock is released
if (!locked){
this.model = model;
}
}
I did a quick workaround by using a Boolean to lock and unlock as shown in updateModel Method, problem with that is it will lose some updated data because it's skipped entirely.. instead, I want it to run later when lock is released.. how can I implement this kind of lock mechanism and run later feature?
Edit: why I suspect this is because my listeners are manipulating and getting table data.. while the data is constantly updated, I'm not sure if this is causing my table to break.
Just write some logic that collects everything you tried to do in a locked state and executes it on unlocking.
The following code assumes you're using Platform.runLater or similar code that makes the update run on the application thread.
public class UpdateSynchronizer {
private final List<Runnable> pendingUpdates = new ArrayList<>();
private boolean locked = false;
public void lock() {
if (locked) {
throw new IllegalStateException("double lock");
} else {
locked = true;
}
}
public void runUpdate(Runnable updater) {
if (updater == null) {
throw new IllegalArgumentException();
}
if (locked) {
pendingUpdates.add(updater);
} else {
updater.run();
}
}
public void unlock() {
for (Runnable r : pendingUpdates) {
try {
r.run();
} catch(Exception ex) {
ex.printStackTrace(); // print but ignore
}
}
pendingUpdates.clear();
locked = false;
}
}
If the last update always overwrites all the data from previous updates, simply keeping a single Runnable instead of a list of them would be more performant.
private final UpdateSynchronizer synchronizer = new UpdateSynchronizer();
// why did all the keywords start with uppercase letters (compile time error)
private void handleEvent(){
TableView.setRowFactory(new Callback<TableView<myModel>, TableRow<myModel>>(){
public TableRow<myModel> call(TableView<myModel> p) {
final TableRow row = new TableRow();
row.setOnDragDetected(new EventHandler<MouseEvent>(){
public void handle(){
synchronizer.lock();
//implement some kind of lock to prevent receiving data update
}
}
row.setOnMouseDragExited(new EventHandler<MouseDragEvent>(){
//release lock to accept update
synchronizer.unlock();
}
}
}
//this method is being called externally periodically
public void updateModel(myModel model){
synchronizer.runUpdate(() -> {
// this is just an assignment and won't have any side effects
// updates to the scene may only happen, if the model is accessed in some event handler or animation
this.model = model;
});
}
I was looking for a universal way (i.e., that can be applied to an arbitrary TableView or TreeTableView) to copy data "as you see it" from a TableView/TreeTableView. I found a couple of posts about how to copy contents from a TableView (here and here), but the important part "as you see it" is the issue with all of them.
All solutions I saw are relying on getting the data associated with each cell (pretty easy to do), and calling .toString() on it. The problem is that when you store one type (let's say a Long) as actual data in a column, and then define a custom cell factory to display it as a String (it's beyond the scope why you would do that, I just want a method that works with such table views):
TableColumn<MyData, Long> timeColumn;
<...>
timeColumn.setCellFactory(param -> new TableCell<MyData, Long>() {
#Override
protected void updateItem(Long item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
super.setText(null);
} else {
super.setText(LocalDate.from(Instant.ofEpochMilli(item)).format(DateTimeFormatter.ISO_DATE));
}
}
}
);
Those methods based on converting the underlying data (which is Long here) to String will obviously not work, because they will copy a number and not a date (which is what the user sees in the table).
Possible (envisioned) solutions:
If I could get my hand on the TableCell object associated with each table cell, I could do TableCell.getText(), and we are done. Unfortunately, TableView does not allow this (have I missed a way to do it?)
I can easily get the CellFactory associated with the column, and therefore create a new TableCell (identical to that one existing in the table view):
TableCell<T, ?> cell = column.getCellFactory().call(column);
Then the problem is there's no way (again, did I miss it?) to force a TableCell to call the updateItem method! I tried to use commitEdit(T newValue), but it's pretty messy: there are checks inside, so you need to make the whole stuff (column, row, table) Editable, and call startEdit first.
2a. So the only solution that works for me, uses the Reflection to call the protected updateItem, which feels kind of dirty hacking:
// For TableView:
T selectedItem = <...>;
// OR for TreeTableView:
TreeItem<T> selectedItem = <...>;
TableCell<T, Object> cell = (TableCell<T, Object>) column.getCellFactory().call(column);
try {
Method update = cell.getClass().getDeclaredMethod("updateItem", Object.class, boolean.class);
update.setAccessible(true);
Object data = column.getCellData(selectedItem);
update.invoke(cell, data, data == null);
} catch (Exception ex) {
logger.warn("Failed to update item: ", ex);
}
if (cell.getText() != null) {
return cell.getText().replaceAll(fieldSeparator, "");
} else {
return "";
}
I would appreciate any comment on it, namely if this can be achieved with less blood. Or may be indicate some problems with my solution which I missed.
Here's the full code in case someone wants to use it (in spite of its ugliness :)
package com.mycompany.util;
import com.google.common.collect.Lists;
import javafx.beans.property.ObjectProperty;
import javafx.scene.control.*;
import javafx.scene.input.Clipboard;
import javafx.scene.input.DataFormat;
import javafx.scene.input.KeyCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
public class TableViewCopyable {
private static final Logger logger = LoggerFactory.getLogger(TableViewCopyable.class.getName());
protected final String defaultFieldSep;
protected final String defaultLineSep;
protected TableViewCopyable(String defaultFieldSep, String defaultLineSep) {
this.defaultFieldSep = defaultFieldSep;
this.defaultLineSep = defaultLineSep;
}
protected static final <T> void copyToClipboard(List<T> rows, Function<List<T>, String> extractor) {
logger.info("Copied " + rows.size() + " item(s) to clipboard");
Clipboard.getSystemClipboard().setContent(Collections.singletonMap(DataFormat.PLAIN_TEXT, extractor.apply(rows)));
}
public static TableViewCopyable with(String fieldSep, String lineSep) {
return new TableViewCopyable(fieldSep, lineSep);
}
public static TableViewCopyable toCsv() {
// When using System.lineSeparator() as line separator, there appears to be an extra line break :-/
return with(",", "\n");
}
public final <T> void makeCopyable(TableView<T> table, Function<List<T>, String> extractor) {
table.setOnKeyPressed(event -> {
if (event.getCode().equals(KeyCode.C) && event.isControlDown() || event.isControlDown() && event.getCode().equals(KeyCode.INSERT)) {
// "Smart" copying: if single selection, copy all by default. Otherwise copy selected by default
boolean selectedOnly = table.getSelectionModel().getSelectionMode().equals(SelectionMode.MULTIPLE);
copyToClipboard(getItemsToCopy(table, selectedOnly), extractor);
}
});
MenuItem copy = new MenuItem("Copy selected");
copy.setOnAction(event -> copyToClipboard(table.getSelectionModel().getSelectedItems(), extractor));
MenuItem copyAll = new MenuItem("Copy all");
copyAll.setOnAction(event -> copyToClipboard(table.getItems(), extractor));
addToContextMenu(table.contextMenuProperty(), copy, copyAll);
}
public final <T> void makeCopyable(TreeTableView<T> table, Function<List<TreeItem<T>>, String> extractor) {
table.setOnKeyPressed(event -> {
if (event.getCode().equals(KeyCode.C) && event.isControlDown() || event.isControlDown() && event.getCode().equals(KeyCode.INSERT)) {
// "Smart" copying: if single selection, copy all by default. Otherwise copy selected by default
boolean selectedOnly = table.getSelectionModel().getSelectionMode().equals(SelectionMode.MULTIPLE);
copyToClipboard(getItemsToCopy(table, selectedOnly), extractor);
}
});
MenuItem copy = new MenuItem("Copy selected");
copy.setOnAction(event -> copyToClipboard(getItemsToCopy(table, true), extractor));
MenuItem copyAll = new MenuItem("Copy all (expanded only)");
copyAll.setOnAction(event -> copyToClipboard(getItemsToCopy(table, false), extractor));
addToContextMenu(table.contextMenuProperty(), copy, copyAll);
}
protected <T> List<TreeItem<T>> getItemsToCopy(TreeTableView<T> table, boolean selectedOnly) {
if (selectedOnly) {
// If multiple selection is allowed, copy only selected by default:
return table.getSelectionModel().getSelectedItems();
} else {
// Otherwise, copy everything
List<TreeItem<T>> list = Lists.newArrayList();
for (int i = 0; i < table.getExpandedItemCount(); i++) {
list.add(table.getTreeItem(i));
}
return list;
}
}
protected <T> List<T> getItemsToCopy(TableView<T> table, boolean selectedOnly) {
if (selectedOnly) {
// If multiple selection is allowed, copy only selected by default:
return table.getSelectionModel().getSelectedItems();
} else {
return table.getItems();
}
}
protected void addToContextMenu(ObjectProperty<ContextMenu> menu, MenuItem... items) {
if (menu.get() == null) {
menu.set(new ContextMenu(items));
} else {
for (MenuItem item : items) {
menu.get().getItems().add(item);
}
}
}
public final <T> void makeCopyable(TableView<T> table, String fieldSeparator) {
makeCopyable(table, csvVisibleColumns(table, fieldSeparator));
}
public final <T> void makeCopyable(TreeTableView<T> table, String fieldSeparator) {
makeCopyable(table, csvVisibleColumns(table, fieldSeparator));
}
public final <T> void makeCopyable(TableView<T> table) {
makeCopyable(table, csvVisibleColumns(table, defaultFieldSep));
}
public final <T> void makeCopyable(TreeTableView<T> table) {
makeCopyable(table, defaultFieldSep);
}
protected <T> String extractDataFromCell(IndexedCell<T> cell, Object data, String fieldSeparator) {
try {
Method update = cell.getClass().getDeclaredMethod("updateItem", Object.class, boolean.class);
update.setAccessible(true);
update.invoke(cell, data, data == null);
} catch (Exception ex) {
logger.warn("Failed to updated item: ", ex);
}
if (cell.getText() != null) {
return cell.getText().replaceAll(fieldSeparator, "");
} else {
return "";
}
}
public final <T> Function<List<T>, String> csvVisibleColumns(TableView<T> table, String fieldSeparator) {
return (List<T> items) -> {
StringBuilder builder = new StringBuilder();
// Write table header
builder.append(table.getVisibleLeafColumns().stream().map(TableColumn::getText).collect(Collectors.joining(fieldSeparator))).append(defaultLineSep);
items.forEach(item -> builder.append(
table.getVisibleLeafColumns()
.stream()
.map(col -> extractDataFromCell(((TableColumn<T, Object>) col).getCellFactory().call((TableColumn<T, Object>) col), col.getCellData(item), fieldSeparator))
.collect(Collectors.joining(defaultFieldSep))
).append(defaultLineSep));
return builder.toString();
};
}
public final <T> Function<List<TreeItem<T>>, String> csvVisibleColumns(TreeTableView<T> table, String fieldSeparator) {
return (List<TreeItem<T>> items) -> {
StringBuilder builder = new StringBuilder();
// Write table header
builder.append(table.getVisibleLeafColumns().stream().map(TreeTableColumn::getText).collect(Collectors.joining(fieldSeparator))).append(defaultLineSep);
items.forEach(item -> builder.append(
table.getVisibleLeafColumns()
.stream()
.map(col -> extractDataFromCell(((TreeTableColumn<T, Object>) col).getCellFactory().call((TreeTableColumn<T, Object>) col), col.getCellData(item), fieldSeparator))
.collect(Collectors.joining(defaultFieldSep))
).append(defaultLineSep));
return builder.toString();
};
}
}
Then the usage is pretty simple:
TableViewCopyable.toCsv().makeCopyable(someTreeTableView);
TableViewCopyable.toCsv().makeCopyable(someTableView);
Thanks!
Can anybody help telling me what is wrong with my code? I am trying to connect to SQLite database, and executing some queries. when trying to create and open the database, create and insert the table, no exception returned. but when I try to execute delete statement,
DatabaseIOException: File system error (12)
always returned. I don't know the cause of the exception exactly. would you tell me what usually cause this kind of exception? I don't even know when I need to close the database and when I don't need to. this solution also makes me confused.
here is my code:
public class DatabaseManager {
Logger log = new Logger();
Database db;
public DatabaseManager() {
createDatabase();
}
private void createDatabase() {
// Determine if an SDCard is present
boolean sdCardPresent = false;
String root = null;
Enumeration enum = FileSystemRegistry.listRoots();
while (enum.hasMoreElements()) {
root = (String) enum.nextElement();
if(root.equalsIgnoreCase("sdcard/")) {
sdCardPresent = true;
}
}
if(!sdCardPresent) {
alert("This application requires an SD card to be present. Exiting application...");
}
else {
try {
URI uri = URI.create("/SDCard/databases/MyAdvanceUI/myadvanceui.db");
db = DatabaseFactory.openOrCreate(uri);
db.close();
//alert("Database OK!");
} catch (Exception e) {
// TODO Auto-generated catch block
//alert("Exception in createDatabase(): " + e);
}
}
}
private void alert(final String message) {
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
Dialog.inform(message);
System.exit(0);
}
});
}
private void createTableTask() {
try {
URI uri = URI.create("/SDCard/databases/MyAdvanceUI/myadvanceui.db");
db = DatabaseFactory.open(uri);
Statement st = db.createStatement("CREATE TABLE IF NOT EXISTS t_task (id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "client TEXT, task TEXT)");
st.prepare();
st.execute();
st.close();
db.close();
//alert("Table Task created!");
} catch (Exception e) {
// TODO: handle exception
//alert("Exception in createTableTask(): " + e);
}
}
private void insertTableTask() {
String[] clients = { "Budi Setiawan", "Dian Kusuma", "Joko Ahmad", "Titi Haryanto", "Wahyu" };
String[] tasks = {
"Penawaran terhadap instalasi server",
"Follow up untuk keperluan produk terbaru",
"Pendekatan untuk membina relasi",
"Penawaran jasa maintenance",
"Penawaran terhadap instalasi database"
};
try {
URI uri = URI.create("/SDCard/databases/MyAdvanceUI/myadvanceui.db");
db = DatabaseFactory.open(uri);
for(int i = 0; i < clients.length; i++) {
Statement st = db.createStatement("INSERT INTO t_task (client, task) VALUES (?, ?)");
st.prepare();
st.bind(1, clients[i]);
st.bind(2, tasks[i]);
st.execute();
st.close();
}
db.close();
} catch (Exception e) {
// TODO: handle exception
//alert("Exception in insertTableTask(): " + e);
}
}
public void loadInitialData() {
createTableTask();
insertTableTask();
}
public Cursor getTasks() {
// TODO Auto-generated method stub
Cursor results = null;
try {
URI uri = URI.create("/SDCard/databases/MyAdvanceUI/myadvanceui.db");
db = DatabaseFactory.open(uri);
Statement st = db.createStatement("SELECT client, task FROM t_task");
st.prepare();
results = st.getCursor();
return results;
} catch (Exception e) {
// TODO: handle exception
//alert("Exception: " + e);
}
return results;
}
public void delete(String string) {
// TODO Auto-generated method stub
try {
URI uri = URI.create("/SDCard/databases/MyAdvanceUI/myadvanceui.db");
db = DatabaseFactory.open(uri);
Statement st = db.createStatement("DELETE FROM t_task WHERE client=?");
st.prepare();
st.bind(1, string);
st.execute();
} catch (Exception e) {
// TODO: handle exception
alert("Exception: " + e);
}
}
}
thank you for your help.
I don't see that you close the statement and close the database after select and delete actions. Most probably you can't open database because it wasn't closed correctly.
Big warning SD card isn't available when user mounted devices to PC as external drive. Some devices are going without SD card preinstalled. DB operations are really slow on 5 OS devices. Your alert method code wan't close db what could be issue to open it after on the next application start.
Warning As #pankar mentioned in comment you should add finally {} where you will close resources for sure. In your current implementation if you get exception in execution you will never close database.
Big improvements You don't need to create and prepare statement every loop. Just do it before for. Do bind and execute every loop. And close statement after for.
Improvements You could keep one opened db during application run cycle. It will save you some line of code and time for opening closing.
Notation It's bad practice to have parameter named like 'string'. I would rename it to something more meaningful.