/* | |
* Copyright (C) 2012 The Android Open Source Project | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
package com.motorolamobility.studio.android.db.core.ui; | |
import java.util.ArrayList; | |
import java.util.HashSet; | |
import java.util.List; | |
import java.util.Set; | |
import org.eclipse.core.runtime.IPath; | |
import org.eclipse.core.runtime.IStatus; | |
import org.eclipse.core.runtime.NullProgressMonitor; | |
import org.eclipse.core.runtime.Status; | |
import org.eclipse.datatools.connectivity.ConnectionProfileException; | |
import org.eclipse.datatools.modelbase.sql.tables.Table; | |
import org.eclipse.datatools.sqltools.result.ResultsViewAPI; | |
import org.eclipse.datatools.sqltools.result.core.IResultManagerListener; | |
import org.eclipse.jface.resource.ImageDescriptor; | |
import org.eclipse.osgi.util.NLS; | |
import org.eclipse.swt.widgets.Display; | |
import org.eclipse.ui.IEditorPart; | |
import org.eclipse.ui.IWorkbenchPage; | |
import com.motorola.studio.android.common.log.StudioLogger; | |
import com.motorola.studio.android.common.utilities.EclipseUtils; | |
import com.motorolamobility.studio.android.db.core.DbCoreActivator; | |
import com.motorolamobility.studio.android.db.core.exception.MotodevDbException; | |
import com.motorolamobility.studio.android.db.core.i18n.DbCoreNLS; | |
import com.motorolamobility.studio.android.db.core.model.DbModel; | |
import com.motorolamobility.studio.android.db.core.model.TableModel; | |
/** | |
* This class represents a database node on the DB Explorer tree view | |
*/ | |
public class DbNode extends AbstractTreeNode implements IDbNode | |
{ | |
/** | |
* Properties name space. | |
*/ | |
public static final String PROP_NAMESPACE = "com.motorolamobility.studio.android.db.core"; //$NON-NLS-1$ | |
/** | |
* Property value used to check if the database is disconnected. | |
*/ | |
public static final String PROP_VALUE_DB_DISCONNECTED = | |
"com.motorolamobility.studio.android.db.core.databaseDisconnected"; //$NON-NLS-1$ | |
/** | |
* Property value used to check if the database is connected. | |
*/ | |
public static final String PROP_VALUE_DB_CONNECTED = | |
"com.motorolamobility.studio.android.db.core.databaseConnected"; //$NON-NLS-1$ | |
/** | |
* Property name used to check database connection status (connected/disconnected). | |
*/ | |
public static final String PROP_NAME_DB_CONNECTION = | |
"com.motorolamobility.studio.android.db.core.databaseConnection"; //$NON-NLS-1$ | |
/** | |
* Property name used to check database connection status (connected/disconnected). | |
*/ | |
public static final String PROP_NAME_DB_NODE_TYPE = | |
"com.motorolamobility.studio.android.db.core.IDbNodeType"; //$NON-NLS-1$ | |
/** | |
* Property value used to check if the database is connected. | |
*/ | |
public static final String PROP_VALUE_DB_NODE_IS_EXT_STORAGE = | |
"com.motorolamobility.studio.android.db.core.isExternalStorage"; //$NON-NLS-1$ | |
private class ResultManagerAdapter extends AbstractDbResultManagerAdapter | |
{ | |
/* (non-Javadoc) | |
* @see com.motorolamobility.studio.android.db.core.ui.AbstractDbResultManagerAdapter#statementExecuted(java.lang.String, java.lang.String) | |
*/ | |
@Override | |
public void statementExecuted(String profileName, String sqlStatement) | |
{ | |
if (model.getProfileName().equals(profileName)) | |
{ | |
//Ignore group execution and read access to table(Select). We'll handle only db changes. | |
if ((!sqlStatement.equals("Group Execution")) //$NON-NLS-1$ | |
&& (sqlStatement.indexOf("select") != 0) && (!sqlStatement.equals(""))) //$NON-NLS-1$ //$NON-NLS-2$ | |
{ | |
if (sqlStatement.startsWith("drop table") //$NON-NLS-1$ | |
|| sqlStatement.startsWith("create table")) //$NON-NLS-1$ | |
{ | |
//A new table has been created let's refresh the dbNode in order to get a update copy | |
refreshAsync(); | |
} | |
else if (sqlStatement.startsWith("alter table")) //$NON-NLS-1$ | |
{ | |
if (!sqlStatement.contains("rename")) //$NON-NLS-1$ | |
{ | |
//Table has been altered but not renamed, let's refresh the table node, loading the possible changes | |
String tableName = sqlStatement.replace("alter table ", ""); //$NON-NLS-1$ //$NON-NLS-2$ | |
tableName.substring(0, tableName.indexOf(" ")); //$NON-NLS-1$ | |
List<ITreeNode> children = getChildren(); | |
for (ITreeNode child : children) | |
{ | |
if (child.getName().equalsIgnoreCase(tableName)) | |
{ | |
child.refreshAsync(); | |
break; | |
} | |
} | |
} | |
else | |
{ | |
//Since a name has been renamed a refresh on this db node will force to use the latest information. | |
refreshAsync(); | |
} | |
} | |
} | |
} | |
} | |
}; | |
public static final String ICON_PATH = "icons/obj16/dbplate.gif"; //$NON-NLS-1$ | |
protected DbModel model; | |
private IResultManagerListener resultManagerListener; | |
protected boolean forceCloseEditors; | |
@SuppressWarnings("unused") | |
private DbNode() | |
{ | |
//Forcing user to use a proper constructor (with a parent) | |
} | |
protected DbNode(ITreeNode parent) | |
{ | |
super(parent); | |
} | |
/** | |
* Creates a new DBNode by using a existent db file. | |
* @param dbFilePath The SQLite database File | |
* @param parent The parent of the new node. | |
* @throws MotodevDbException | |
*/ | |
public DbNode(IPath dbFilePath, ITreeNode parent) throws MotodevDbException | |
{ | |
this(parent); | |
init(dbFilePath); | |
model = new DbModel(dbFilePath); | |
} | |
/** | |
* Creates a new DBNode by creating a new SQLite3 database file if requested. | |
* @param dbPath The SQLite database File | |
* @param parent The parent of the new node. | |
* @param create set this flag to true if you want to create a new db file, if the flag is false the behavior is the same as the constructor DbNode(Path dbFilePath, AbstractTreeNode parent) | |
* @throws MotodevDbException | |
*/ | |
public DbNode(IPath dbPath, ITreeNode parent, boolean create) throws MotodevDbException | |
{ | |
this(parent); | |
init(dbPath); | |
try | |
{ | |
model = new DbModel(dbPath, create); | |
} | |
catch (MotodevDbException e) | |
{ | |
throw new MotodevDbException("Could not create DBNode", e); //$NON-NLS-1$ | |
} | |
} | |
/* (non-Javadoc) | |
* @see com.motorolamobility.studio.android.db.core.ui.IDBNode#connect() | |
*/ | |
public IStatus connect() | |
{ | |
IStatus status = model.connect(); | |
if (status.isOK()) | |
{ | |
if (resultManagerListener == null) | |
{ | |
resultManagerListener = new ResultManagerAdapter(); | |
ResultsViewAPI.getInstance().getResultManager() | |
.addResultManagerListener(resultManagerListener); | |
} | |
} | |
setNodeStatus(status); | |
return status; | |
} | |
/* (non-Javadoc) | |
* @see com.motorolamobility.studio.android.db.core.ui.IDBNode#disconnect() | |
*/ | |
public IStatus disconnect() | |
{ | |
IStatus status = closeAssociatedEditors(); | |
if (status.isOK()) | |
{ | |
status = model.disconnect(); | |
if (status.isOK()) | |
{ | |
if (resultManagerListener != null) | |
{ | |
ResultsViewAPI.getInstance().getResultManager() | |
.removeResultManagerListener(resultManagerListener); | |
resultManagerListener = null; | |
} | |
clear(); | |
} | |
if (status.getSeverity() != IStatus.CANCEL) | |
{ | |
setNodeStatus(status); | |
} | |
} | |
if (status.getSeverity() != IStatus.CANCEL) | |
{ | |
setNodeStatus(status); | |
} | |
return status; | |
} | |
public IStatus createTables(List<TableModel> tables) | |
{ | |
IStatus status = Status.OK_STATUS; | |
List<ITreeNode> tableNodes = new ArrayList<ITreeNode>(tables.size()); | |
for (TableModel table : tables) | |
{ | |
status = model.createTable(table); | |
if (status.isOK()) | |
{ | |
TableNode tableNode = new TableNode(getTable(table.getName()), model, this); | |
tableNodes.add(tableNode); | |
} | |
else | |
{ | |
break; | |
} | |
} | |
if (status.isOK()) | |
{ | |
putChildren(tableNodes); | |
} | |
return status; | |
} | |
/* (non-Javadoc) | |
* @see com.motorolamobility.studio.android.db.core.ui.IDBNode#createTable(java.lang.String, java.lang.String) | |
*/ | |
public IStatus createTable(TableModel table) | |
{ | |
IStatus status = model.createTable(table); | |
if (status.isOK()) | |
{ | |
TableNode tableNode = new TableNode(getTable(table.getName()), model, this); | |
putChild(tableNode); | |
} | |
return status; | |
} | |
/* (non-Javadoc) | |
* @see com.motorolamobility.studio.android.db.core.ui.IDBNode#deleteTable(java.lang.String) | |
*/ | |
public IStatus deleteTable(ITableNode tableNode) | |
{ | |
IStatus status = model.deleteTable(tableNode.getName()); | |
if (status.isOK()) | |
{ | |
removeChild(tableNode); | |
} | |
return status; | |
} | |
/* (non-Javadoc) | |
* @see com.motorolamobility.studio.android.db.core.ui.IDBNode#getTables() | |
*/ | |
public List<Table> getTables() | |
{ | |
return model.getTables(); | |
} | |
/* (non-Javadoc) | |
* @see com.motorolamobility.studio.android.db.core.ui.AbstractTreeNode#refresh() | |
*/ | |
@Override | |
public void refresh() | |
{ | |
if (!model.isConnected()) | |
{ | |
connect(); | |
} | |
clear(); | |
List<Table> tables = getTables(); | |
List<ITreeNode> tableNodes = new ArrayList<ITreeNode>(tables.size()); | |
for (Table table : tables) | |
{ | |
TableNode tableNode = new TableNode(table, model, this); | |
tableNodes.add(tableNode); | |
} | |
putChildren(tableNodes); | |
} | |
protected IStatus closeAssociatedEditors(final boolean quiet, final boolean forceClose) | |
{ | |
Set<IEditorPart> associatedEditors = getAssociatedEditors(); | |
IStatus status = Status.OK_STATUS; | |
if (!associatedEditors.isEmpty()) | |
{ | |
final boolean[] success = new boolean[] | |
{ | |
true | |
}; | |
for (final IEditorPart editor : associatedEditors) | |
{ | |
final IWorkbenchPage page = EclipseUtils.getPageForEditor(editor); | |
Display.getDefault().syncExec(new Runnable() | |
{ | |
public void run() | |
{ | |
if (!quiet) | |
{ | |
page.bringToTop(editor); | |
if (!forceClose) | |
{ | |
//Use the default Eclipse behavior | |
if (!page.closeEditor(editor, true)) | |
{ | |
success[0] = false; | |
} | |
} | |
else | |
{ | |
if (editor.isDirty()) | |
{ | |
//Use our dialog, because the operation can't be cancelled. | |
boolean shallSave = | |
EclipseUtils.showQuestionDialog( | |
DbCoreNLS.DbNode_Close_Editor_Msg_Title, | |
NLS.bind(DbCoreNLS.DbNode_Close_Editor_Msg, | |
getName())); | |
if (shallSave) | |
{ | |
editor.doSave(new NullProgressMonitor()); | |
} | |
} | |
page.closeEditor(editor, false); | |
} | |
} | |
else | |
{ | |
page.closeEditor(editor, false); | |
} | |
} | |
}); | |
if (!success[0]) | |
{ | |
break; | |
} | |
} | |
if (!success[0]) | |
{ | |
status = | |
new Status(IStatus.CANCEL, DbCoreActivator.PLUGIN_ID, | |
DbCoreNLS.DbNode_Canceled_Save_Operation); | |
} | |
} | |
return status; | |
} | |
/** | |
* @param status | |
* @return | |
*/ | |
protected IStatus closeAssociatedEditors() | |
{ | |
return closeAssociatedEditors(false, forceCloseEditors); | |
} | |
/* (non-Javadoc) | |
* @see com.motorolamobility.studio.android.db.core.ui.IDBNode#getTable(java.lang.String) | |
*/ | |
public Table getTable(String tableName) | |
{ | |
return model.getTable(tableName); | |
} | |
private void init(IPath dbFilePath) | |
{ | |
String dbFileName = dbFilePath.lastSegment(); | |
String id = dbFilePath.toFile().getParent() + "." + dbFileName; //$NON-NLS-1$ | |
setId(id); | |
setName(dbFileName); | |
ImageDescriptor icon = | |
DbCoreActivator.imageDescriptorFromPlugin(DbCoreActivator.PLUGIN_ID, ICON_PATH); | |
setIcon(icon); | |
setToolTip(dbFilePath); | |
} | |
/* | |
* Sets the tool tip for the node given its path. | |
*/ | |
private void setToolTip(IPath dbPath) | |
{ | |
//for mapped nodes (i.e., children of IDbMapperNode) the tool tip is its path | |
if (getParent() instanceof IDbMapperNode) | |
{ | |
setTooltip(NLS.bind(DbCoreNLS.DbNode_Tooltip_Prefix, dbPath.toString())); | |
} | |
} | |
/* (non-Javadoc) | |
* @see com.motorolamobility.studio.android.db.core.ui.AbstractTreeNode#isLeaf() | |
*/ | |
@Override | |
public boolean isLeaf() | |
{ | |
return false; | |
} | |
/* (non-Javadoc) | |
* @see com.motorolamobility.studio.android.db.core.ui.IDbNode#deleteDb() | |
*/ | |
public IStatus deleteDb() | |
{ | |
closeAssociatedEditors(true, forceCloseEditors); | |
disconnect(); | |
return model.deleteDb(); | |
} | |
/* (non-Javadoc) | |
* @see org.eclipse.ui.IActionFilter#testAttribute(java.lang.Object, java.lang.String, java.lang.String) | |
*/ | |
@Override | |
public boolean testAttribute(Object target, String name, String value) | |
{ | |
boolean result = false; | |
//check if 'name' is a specific DbNode property | |
if (name.equals(PROP_NAME_DB_CONNECTION) | |
|| PROP_NAME_DB_CONNECTION.equals(PROP_NAMESPACE + '.' + name)) | |
{ | |
if (value.equals(PROP_VALUE_DB_CONNECTED)) | |
{ | |
result = isConnected(); | |
} | |
else if (value.equals(PROP_VALUE_DB_DISCONNECTED)) | |
{ | |
result = !isConnected(); | |
} | |
} | |
else if (name.equals(PROP_NAME_DB_NODE_TYPE)) | |
{ | |
if (value.equals(PROP_VALUE_DB_NODE_IS_EXT_STORAGE)) | |
{ | |
result = (getParent() instanceof IDbMapperNode); | |
} | |
} | |
else | |
{ | |
//check if 'name' is a generic ITreeNode property | |
result = super.testAttribute(target, name, value); | |
} | |
return result; | |
} | |
/* (non-Javadoc) | |
* @see com.motorolamobility.studio.android.db.core.ui.AbstractTreeNode#getIcon() | |
*/ | |
@Override | |
public ImageDescriptor getIcon() | |
{ | |
return getSpecificIcon("org.eclipse.datatools.connectivity.sqm.core.ui", //$NON-NLS-1$ | |
"icons/database.gif"); //$NON-NLS-1$ | |
} | |
public boolean isConnected() | |
{ | |
return model != null ? model.isConnected() : false; | |
} | |
/* (non-Javadoc) | |
* @see com.motorolamobility.studio.android.db.core.ui.AbstractTreeNode#clean() | |
*/ | |
@Override | |
public void cleanUp() | |
{ | |
setForceCloseEditors(true); | |
disconnect(); | |
if (model != null) | |
{ | |
try | |
{ | |
model.cleanModel(); | |
} | |
catch (ConnectionProfileException e) | |
{ | |
StudioLogger.debug(this, "Unable to cleanup db model."); | |
} | |
} | |
super.cleanUp(); | |
} | |
/** | |
* Retrieves the open editors that is used to edit the given profile, if any | |
* | |
* @param profile | |
* The profile that owns the requested editor | |
* @return The open dirty editor for the given profile, or <code>null</code> | |
* if there is no editor in this condition | |
*/ | |
public Set<IEditorPart> getAssociatedEditors() | |
{ | |
return model != null ? model.getAssociatedEditors() : new HashSet<IEditorPart>(0); | |
} | |
/** | |
* Checks if the db file exists in filesystem | |
* @return | |
*/ | |
public boolean existsDbFile() | |
{ | |
return model != null ? model.getDbPath().toFile().exists() : false; | |
} | |
public IPath getPath() | |
{ | |
return model != null ? model.getDbPath() : null; | |
} | |
/** | |
* @param forceCloseEditors the forceCloseEditors to set | |
*/ | |
protected void setForceCloseEditors(boolean forceCloseEditors) | |
{ | |
this.forceCloseEditors = forceCloseEditors; | |
} | |
} |