blob: 01580465f411b905eff2e72488c405e39cbaa280 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2009, 2020 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.internal.r.core.sourcemodel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.sql.DataSource;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.ImList;
import org.eclipse.statet.ecommons.collections.IntArrayMap;
import org.eclipse.statet.ecommons.collections.IntMap;
import org.eclipse.statet.ecommons.edb.core.EmbeddedDB;
import org.eclipse.statet.internal.r.core.RCorePlugin;
import org.eclipse.statet.internal.r.core.RProjectNature;
import org.eclipse.statet.internal.r.core.builder.CompositeFrame;
import org.eclipse.statet.internal.r.core.builder.RBuildReconciler;
import org.eclipse.statet.internal.r.core.builder.RPkgData;
import org.eclipse.statet.internal.r.core.builder.RPkgReconciler;
import org.eclipse.statet.internal.r.core.builder.RUnitElement;
import org.eclipse.statet.ltk.core.LTK;
import org.eclipse.statet.ltk.model.core.ISourceUnitManager;
import org.eclipse.statet.ltk.model.core.elements.ISourceUnit;
import org.eclipse.statet.r.core.RCore;
import org.eclipse.statet.r.core.RProject;
import org.eclipse.statet.r.core.model.IRFrame;
import org.eclipse.statet.r.core.model.IRModelInfo;
import org.eclipse.statet.r.core.model.IRSourceUnit;
import org.eclipse.statet.r.core.model.IRWorkspaceSourceUnit;
import org.eclipse.statet.r.core.model.RElementName;
import org.eclipse.statet.r.core.model.RModel;
import org.eclipse.statet.r.core.model.RModelIndexUpdate;
import org.eclipse.statet.r.core.model.RSuModelContainer;
public class RModelIndex {
/** DB definitions */
@SuppressWarnings({ "hiding", "nls" })
private static final class RIndex {
static final String NAME= "RINDEX";
static final String VERSION= "18";
static final class Properties {
static final String NAME= "PROPERTIES";
static final String QNAME= RIndex.NAME + '.' + NAME;
static final String COL_NAME= "NAME";
static final String COL_VALUE= "VALUE";
static final String DEFINE_1= "create table " + QNAME + " ("
+ COL_NAME + " varchar(512) not null "
+ "primary key, "
+ COL_VALUE + " varchar(4096)"
+ ")";
}
static final class Models {
static final String NAME="MODELS";
static final String QNAME= RIndex.NAME + '.' + NAME;
/** model type id */
static final String COL_MODEL_TYPE= "MODEL_TYPE";
/** db-id of model type */
static final String COL_ID= "ID";
static final String DEFINE_1= "create table " + QNAME + " ("
+ COL_ID + " int not null "
+ "primary key "
+ "generated always as identity (start with 0), "
+ COL_MODEL_TYPE + " varchar(512) not null, "
+ "unique ("
+ COL_MODEL_TYPE + ")"
+ ")";
static final String OP_insert= "insert into " + QNAME + " ("
+ COL_MODEL_TYPE + ") "
+ "values (?)";
static final String OP_getAll= "select "
+ COL_MODEL_TYPE + ", "
+ COL_ID + " "
+ "from " + QNAME;
}
static final class Projects {
static final String NAME= "PROJECTS";
static final String QNAME= RIndex.NAME + '.' + NAME;
/** name of project */
static final String COL_NAME= "NAME";
/** db-id of project */
static final String COL_ID= "ID";
/** name of R package */
static final String COL_PKG_NAME= "PKG_NAME";
static final String DEFINE_1= "create table " + QNAME + " ("
+ COL_ID + " int not null "
+ "primary key "
+ "generated always as identity, "
+ COL_NAME + " varchar(512) not null, "
+ COL_PKG_NAME + " varchar(512), "
+ "unique ("
+ COL_NAME + ")"
+ ")";
static final String OP_insert= "insert into " + QNAME + " ("
+ COL_NAME + ", "
+ COL_PKG_NAME + ") "
+ "values (?, ?)";
static final String OP_update= "update " + QNAME + " set "
+ COL_PKG_NAME + "= ? "
+ "where (" + COL_ID + "= ?)";
static final String OP_delete= "delete from " + QNAME + " "
+ "where (" + COL_ID + "= ?)";
static final String OP_getAll= "select "
+ COL_ID + ", "
+ COL_NAME + ", "
+ COL_PKG_NAME + " "
+ "from " + QNAME;
}
static final class SourceUnits {
static final String NAME= "SUS";
static final String QNAME= RIndex.NAME + '.' + NAME;
/** db-id of project -> Projects.ID */
static final String COL_PROJECT_ID= "PROJECT_ID";
/** name of source unit */
static final String COL_NAME= "NAME";
/** db-id of source unit */
static final String COL_ID= "ID";
/** db-id of model type */
static final String COL_MODEL_TYPE_ID= "MODEL_TYPE_ID";
static final String DEFINE_1= "create table " + QNAME + " ("
+ COL_ID + " bigint not null "
+ "primary key "
+ "generated always as identity, "
+ COL_PROJECT_ID + " int not null "
+ "references " + Projects.QNAME + " on delete cascade, "
+ COL_NAME + " varchar(4096) not null, "
+ COL_MODEL_TYPE_ID + " int not null "
+ "references " + Models.QNAME + " on delete cascade, "
+ "unique ("
+ COL_PROJECT_ID + ", "
+ COL_NAME + ")"
+ ")";
static final String OP_insert= "insert into " + QNAME + " ("
+ COL_PROJECT_ID + ", "
+ COL_NAME + ", "
+ COL_MODEL_TYPE_ID + ") "
+ "values (?, ?, ?)";
static final String OP_updateModel= "update " + QNAME + " set "
+ COL_MODEL_TYPE_ID + "= ? "
+ "where (" + COL_ID + "= ?)";
static final String OP_delete_byProjectAndName= "delete from " + QNAME + " "
+ "where (" + COL_PROJECT_ID + "= ? and " + COL_NAME + "= ?)";
static final String OP_deleteAll_ofProject= "delete from " + QNAME + " "
+ "where (" + COL_PROJECT_ID + "= ?)";
static final String OP_deleteAll_ofProjectAndModel= "delete from " + QNAME + " "
+ "where (" + COL_PROJECT_ID + "= ? and " + COL_MODEL_TYPE_ID + "= ?)";
static final String OP_get= "select "
+ COL_ID + ", "
+ COL_MODEL_TYPE_ID + " "
+ "from " + QNAME + " "
+ "where (" + COL_PROJECT_ID + "= ? and " + COL_NAME + "= ?)";
}
static final class NamesIdx {
static final String NAME= "NAMESIDX";
static final String QNAME= RIndex.NAME + '.' + NAME;
/** db-id of name */
static final String COL_ID= "ID";
/** name */
static final String COL_NAME= "NAME";
static final String DEFINE_1= "create table " + QNAME + " ("
+ COL_ID + " bigint not null "
+ "primary key "
+ "generated always as identity, "
+ COL_NAME + " varchar(512) not null, "
+ "unique ("
+ COL_NAME + ")"
+ ")";
static final String OP_insert= "insert into " + QNAME + " ("
+ COL_NAME + ") "
+ "values (?)";
static final String OP_getID= "select "
+ COL_ID + " "
+ "from " + QNAME + " "
+ "where (" + COL_NAME + "= ?)";
}
static final class Exports {
static final String NAME= "EXPORTS";
static final String QNAME= RIndex.NAME + '.' + NAME;
/** db-id of source unit -> SourceUnits.ID */
static final String COL_SU_ID= "SU_ID";
/** blob */
static final String COL_OBJECTDATA= "OBJECTDATA";
static final String DEFINE_1= "create table " + QNAME + " ("
+ COL_SU_ID + " bigint not null "
+ "primary key "
+ "references " + SourceUnits.QNAME + " on delete cascade, "
+ COL_OBJECTDATA + " blob"
+ ")";
static final String OP_insert= "insert into " + QNAME + " ("
+ COL_SU_ID + ", "
+ COL_OBJECTDATA + ") "
+ "values (?, ?)";
static final String OP_update= "update " + QNAME + " set "
+ COL_OBJECTDATA + "= ? "
+ "where (" + COL_SU_ID + "= ?)";
static final String OP_getAll_ofProject= "select "
+ "S." + SourceUnits.COL_NAME + ", "
+ "S." + SourceUnits.COL_MODEL_TYPE_ID + ", "
+ "E." + COL_OBJECTDATA + " "
+ "from " + SourceUnits.QNAME + " as S "
+ "inner join " + QNAME + " as E on (E." + COL_SU_ID + "= S." + SourceUnits.COL_ID + ") "
+ "where (S." + SourceUnits.COL_PROJECT_ID + "= ?)";
}
static final class MainNames {
static final String NAME= "MAINNAMES";
static final String QNAME= RIndex.NAME + '.' + NAME;
/** db-id of source unit -> SourceUnits.ID */
static final String COL_SU_ID= "SU_ID";
/** db-id of name -> Names.ID */
static final String COL_NAME_ID= "NAME_ID";
static final String DEFINE_1= "create table " + QNAME + " ("
+ COL_SU_ID + " bigint not null "
+ "references " + SourceUnits.QNAME + " on delete cascade, "
+ COL_NAME_ID + " bigint not null "
+ "references " + NamesIdx.QNAME + " on delete cascade, "
+ "primary key ("
+ COL_SU_ID + ", "
+ COL_NAME_ID + ")"
+ ")";
static final String OP_insert= "insert into " + QNAME + " ("
+ COL_SU_ID + ", "
+ COL_NAME_ID + ") "
+ "values (?, ?)";
static final String OP_deleteAll_ofSourceUnit= "delete from " + QNAME + " "
+ "where (" + COL_SU_ID + "= ?)";
static final String OP_findSourceUnits_ofProjectAndName= "select "
+ "S." + SourceUnits.COL_NAME + ", "
+ "S." + SourceUnits.COL_MODEL_TYPE_ID + " "
+ "from " + SourceUnits.QNAME + " as S "
+ "inner join " + QNAME + " as M on (M." + COL_SU_ID + "= S." + SourceUnits.COL_ID + ") "
+ "inner join " + NamesIdx.QNAME + " as N on (M." + COL_NAME_ID + "= N." + NamesIdx.COL_ID + ") "
+ "where (S." + SourceUnits.COL_PROJECT_ID + "= ? and N." + NamesIdx.COL_NAME + "= ?)";
}
}
private static final class Proj {
public final int id;
public final String name;
private boolean removed;
private volatile String pkgName;
public Proj(final int id, final String name, final String pkgName) {
this.id= id;
this.name= name;
this.removed= false;
setPkgName(pkgName);
}
public boolean isRemoved() {
return this.removed;
}
public void setPkgName(final String pkgName) {
this.pkgName= (pkgName != null) ? pkgName.intern() : null;
}
public String getPkgName() {
return this.pkgName;
}
@Override
public int hashCode() {
return this.id;
}
@Override
public boolean equals(final Object obj) {
return (this.id == ((Proj) obj).id);
}
}
private static class DbTools {
private static final String MISSING_GENERATED_RESULT= "Unexpected result (generatedKeys).";
public final Connection connection;
public DbTools(final Connection connection) throws SQLException {
this.connection= connection;
this.connection.setAutoCommit(false);
}
private PreparedStatement addModel;
public int addModel(final String modelTypeId) throws SQLException {
if (this.addModel == null) {
this.addModel= this.connection.prepareStatement(RIndex.Models.OP_insert,
new String[] { RIndex.Models.COL_ID } );
}
this.addModel.setString(1, modelTypeId);
this.addModel.executeUpdate();
final ResultSet result= this.addModel.getGeneratedKeys();
if (result.next()) {
return result.getInt(1);
}
throw new SQLException(MISSING_GENERATED_RESULT);
}
private PreparedStatement addProj;
public int addProj(final String name, final String pkgName) throws SQLException {
if (this.addProj == null) {
this.addProj= this.connection.prepareStatement(RIndex.Projects.OP_insert,
new String[] { RIndex.Projects.COL_ID } );
}
this.addProj.setString(1, name);
this.addProj.setString(2, pkgName);
this.addProj.executeUpdate();
final ResultSet result= this.addProj.getGeneratedKeys();
if (result.next()) {
return result.getInt(1);
}
throw new SQLException(MISSING_GENERATED_RESULT);
}
private PreparedStatement updateProj;
public void updateProj(final Proj proj) throws SQLException {
if (this.updateProj == null) {
this.updateProj= this.connection.prepareStatement(RIndex.Projects.OP_update);
}
this.updateProj.setString(1, proj.getPkgName());
this.updateProj.setInt(2, proj.id);
this.updateProj.executeUpdate();
}
private PreparedStatement removeProjStatement;
public void removeProj(final int projId) throws SQLException {
if (this.removeProjStatement == null) {
this.removeProjStatement= this.connection.prepareStatement(RIndex.Projects.OP_delete);
}
this.removeProjStatement.setInt(1, projId);
this.removeProjStatement.executeUpdate();
}
private PreparedStatement clearProjStatement;
public void clearProj(final int projId) throws SQLException {
if (this.clearProjStatement == null) {
this.clearProjStatement= this.connection.prepareStatement(RIndex.SourceUnits.OP_deleteAll_ofProject);
}
this.clearProjStatement.setInt(1, projId);
this.clearProjStatement.executeUpdate();
}
private PreparedStatement clearProjModelStatement;
public void clearProj(final int projId, final int modelId) throws SQLException {
if (this.clearProjModelStatement == null) {
this.clearProjModelStatement= this.connection.prepareStatement(RIndex.SourceUnits.OP_deleteAll_ofProjectAndModel);
}
this.clearProjModelStatement.setInt(1, projId);
this.clearProjModelStatement.setInt(2, modelId);
this.clearProjModelStatement.executeUpdate();
}
public Proj currentProj;
public long currentUnitId;
public boolean currentUnitNew;
private PreparedStatement getUnitStatement;
private PreparedStatement addUnitStatement;
public void prepareUnits(final Proj proj) throws SQLException {
this.currentProj= proj;
if (this.getUnitStatement == null) {
this.getUnitStatement= this.connection.prepareStatement(RIndex.SourceUnits.OP_get);
this.addUnitStatement= this.connection.prepareStatement(RIndex.SourceUnits.OP_insert,
new String[] { RIndex.SourceUnits.COL_ID } );
}
this.getUnitStatement.setInt(1, proj.id);
this.addUnitStatement.setInt(1, proj.id);
}
/** requires {@link #initProjForSu(Proj)}, {@link #prepareUnits(Proj)} */
private PreparedStatement updateUnitModelStatement;
public void executeGetOrAddUnit(final String unitId, final int modelId) throws SQLException {
{ // get / update
this.getUnitStatement.setString(2, unitId);
final ResultSet result= this.getUnitStatement.executeQuery();
if (result.next()) {
this.currentUnitId= result.getLong(1);
this.currentUnitNew= false;
if (result.getInt(2) != modelId) {
if (this.updateUnitModelStatement == null) {
this.updateUnitModelStatement= this.connection.prepareStatement(RIndex.SourceUnits.OP_updateModel);
}
this.updateUnitModelStatement.setInt(1, modelId);
this.updateUnitModelStatement.setLong(2, this.currentUnitId);
this.updateUnitModelStatement.execute();
}
return;
}
}
{ // insert
this.addUnitStatement.setString(2, unitId);
this.addUnitStatement.setInt(3, modelId);
this.addUnitStatement.executeUpdate();
final ResultSet result= this.addUnitStatement.getGeneratedKeys();
if (result.next()) {
this.currentUnitId= result.getLong(1);
this.currentUnitNew= true;
return;
}
throw new SQLException(MISSING_GENERATED_RESULT);
}
}
/** requires {@link #prepareUnits(Proj)} */
public boolean executeGetUnit(final String publicId) throws SQLException {
this.getUnitStatement.setString(2, publicId);
final ResultSet result= this.getUnitStatement.executeQuery();
if (result.next()) {
this.currentUnitId= result.getInt(1);
this.currentUnitNew= false;
return true;
}
return false;
}
/** requires {@link #prepareUnits(Proj)} */
private PreparedStatement removeUnitStatement;
public void executeRemoveUnit(final String unitId) throws SQLException {
if (this.removeUnitStatement == null) {
this.removeUnitStatement= this.connection.prepareStatement(RIndex.SourceUnits.OP_delete_byProjectAndName);
}
this.removeUnitStatement.setInt(1, this.currentProj.id);
this.removeUnitStatement.setString(2, unitId);
this.removeUnitStatement.executeUpdate();
}
private PreparedStatement removeUnitMainNamesStatement;
public void clearUnitNames() throws SQLException {
if (this.removeUnitMainNamesStatement == null) {
this.removeUnitMainNamesStatement= this.connection.prepareStatement(RIndex.MainNames.OP_deleteAll_ofSourceUnit);
}
this.removeUnitMainNamesStatement.setLong(1, this.currentUnitId);
this.removeUnitMainNamesStatement.executeUpdate();
}
private PreparedStatement getNameStatement;
private PreparedStatement addNameStatement;
public long getOrAddName(final String name) throws SQLException {
if (this.getNameStatement == null) {
this.getNameStatement= this.connection.prepareStatement(RIndex.NamesIdx.OP_getID);
this.addNameStatement= this.connection.prepareStatement(RIndex.NamesIdx.OP_insert,
new String[] { RIndex.NamesIdx.COL_ID } );
}
{ // get
this.getNameStatement.setString(1, name);
final ResultSet result= this.getNameStatement.executeQuery();
if (result.next()) {
return result.getLong(1);
}
}
{ // add
this.addNameStatement.setString(1, name);
this.addNameStatement.executeUpdate();
final ResultSet result= this.addNameStatement.getGeneratedKeys();
if (result.next()) {
return result.getLong(1);
}
throw new SQLException(MISSING_GENERATED_RESULT);
}
}
}
private final ISourceUnitManager sourceUnitManager;
private final RBuildReconciler reconciler;
private final RPkgReconciler pkgParser;
private final ReadWriteLock lock= new ReentrantReadWriteLock();
private int dbInitialized;
private DataSource dbConnectionPool;
private DbTools dbTools;
private final Map<String, Integer> modelType2Id= new HashMap<>();
private final IntMap<String> modelId2Type= new IntArrayMap<>();
private final Map<String, Proj> projects= new ConcurrentHashMap<>();
private final Map<Proj, CompositeFrame> elementsList= new HashMap<>();
public RModelIndex(final RModelManager manager) {
this.sourceUnitManager= LTK.getSourceUnitManager();
this.reconciler= new RBuildReconciler(manager);
this.pkgParser= new RPkgReconciler();
initDB();
}
public void dispose() {
this.lock.writeLock().lock();
try {
this.dbInitialized= 1000;
closeDbTools();
}
finally {
this.lock.writeLock().unlock();
}
}
public void clear(final IProject project) {
final Proj proj= this.projects.get(project.getName());
if (proj != null) {
this.elementsList.remove(proj);
if (this.dbInitialized == 1) {
this.lock.writeLock().lock();
try {
final DbTools tools= getDbTools();
tools.clearProj(proj.id);
tools.connection.commit();
}
catch (final SQLException e) {
onDbToolsError(e);
}
finally {
this.lock.writeLock().unlock();
}
}
}
}
private static final ImList<String> R_MODEL_TYPES= ImCollections.newList(RModel.R_TYPE_ID);
public void update(final RProject rProject,
final IContainer pkgRoot,
final List<IFile> toRemoveRSU, final List<IRWorkspaceSourceUnit> toUpdateRSU,
final MultiStatus status, final SubMonitor m) throws CoreException {
final String projName= rProject.getProject().getName();
final boolean updatePkg= (pkgRoot != null);
m.beginTask("",
((updatePkg) ? 1 : 0) +
((toRemoveRSU != null) ? 1 : 0) );
this.reconciler.init(rProject, status);
final RModelIndexUpdate indexUpdate= new RModelIndexUpdate(rProject, R_MODEL_TYPES,
(toRemoveRSU == null) );
if (updatePkg) {
m.subTask(String.format("Analyzing R package files of '%1$s'...", projName));
indexUpdate.update(this.pkgParser.parsePkgData(pkgRoot, status, m.newChild(1)));
}
m.subTask(String.format("Analyzing R files of '%1$s'...", projName));
if (toRemoveRSU != null) {
for (final IFile toRemove : toRemoveRSU) {
indexUpdate.remove(toRemove);
}
m.worked(1);
}
if (toUpdateRSU != null) {
for (final IRWorkspaceSourceUnit sourceUnit : toUpdateRSU) {
final RSuModelContainer adapter= sourceUnit.getAdapter(RSuModelContainer.class);
if (adapter != null) {
try {
final IRModelInfo model= this.reconciler.build(adapter, m);
indexUpdate.update(sourceUnit, model);
}
catch (final Exception e) {
status.add(new Status(IStatus.ERROR, RCore.BUNDLE_ID, 0,
String.format("An error occurred when indexing '%1$s'.",
sourceUnit.getResource().getFullPath().toString()),
e ));
}
}
}
}
m.subTask(String.format("Updating index for '%1$s'...", projName));
update(indexUpdate, m);
}
public void update(final RModelIndexUpdate order, final IProgressMonitor progress)
throws CoreException {
this.lock.writeLock().lock();
try {
if (this.dbInitialized != 1) {
return; // exception?
}
final Proj proj= getOrCreateProjectId(order.rProject, order.pkgData);
if (proj == null) {
return;
}
CompositeFrame frame= this.elementsList.get(proj);
if (order.pkgData != null) {
doUpdatePkg(proj, order.pkgData, order.rProject, frame);
}
if (order.isFullBuild) {
doClearProj(proj, order.modelTypeIds, frame);
if (order.updated.isEmpty()) {
return;
}
}
if (frame == null) {
// create frame
final DbTools tools= getDbTools();
frame= getFrame(proj, order.rProject.getProject(), tools.connection, progress);
tools.connection.commit();
if (frame == null) {
if (order.isFullBuild) {
frame= new CompositeFrame(this.lock,
proj.getPkgName(), order.projectName );
this.elementsList.put(proj, frame);
}
else {
return;
}
}
}
{ DbTools tools= null;
PreparedStatement updateExportsStatement= null;
PreparedStatement insertExportsStatement= null;
PreparedStatement insertMainNameStatement= null;
final ByteArrayOutputStream byteOutput= new ByteArrayOutputStream();
for (final RModelIndexOrder.Result updated : order.updated) {
if (tools == null) {
tools= getDbTools();
tools.prepareUnits(proj);
insertExportsStatement= tools.connection.prepareStatement(RIndex.Exports.OP_insert);
updateExportsStatement= tools.connection.prepareStatement(RIndex.Exports.OP_update);
insertMainNameStatement= tools.connection.prepareStatement(RIndex.MainNames.OP_insert);
}
final ISourceUnit sourceUnit= updated.exportedElement.getSourceUnit();
final int modelId= getOrCreateModelId(sourceUnit.getModelTypeId());
final RUnitElement previous= frame.setModelElement(updated.unitId, updated.exportedElement);
order.removed.remove(updated.unitId);
tools.executeGetOrAddUnit(updated.unitId, modelId);
byteOutput.reset();
updated.exportedElement.save(byteOutput);
final byte[] objectBytes= byteOutput.toByteArray();
final ByteArrayInputStream objectStream= new ByteArrayInputStream(objectBytes);
try {
if (tools.currentUnitNew) {
insertExportsStatement.setLong(1, tools.currentUnitId);
insertExportsStatement.setBinaryStream(2, objectStream, objectBytes.length);
insertExportsStatement.execute();
}
else {
updateExportsStatement.setLong(2, tools.currentUnitId);
updateExportsStatement.setBinaryStream(1, objectStream, objectBytes.length);
updateExportsStatement.execute();
tools.clearUnitNames();
}
insertMainNameStatement.setLong(1, tools.currentUnitId);
for (final String name : updated.defaultNames) {
if (name != null) {
insertMainNameStatement.setLong(2, tools.getOrAddName(name));
insertMainNameStatement.executeUpdate();
}
}
tools.connection.commit();
}
catch (final SQLException e) {
try {
if (insertExportsStatement != null) {
insertExportsStatement.close();
}
if (updateExportsStatement != null) {
updateExportsStatement.close();
}
if (insertMainNameStatement != null) {
insertMainNameStatement.close();
}
}
catch (final SQLException ignore) {}
onDbToolsError(e);
tools= null;
}
}
if (!order.removed.isEmpty()) {
if (tools == null) {
tools= getDbTools();
tools.prepareUnits(proj);
}
for (final String unitId : order.removed) {
frame.removeModelElement(unitId);
try {
tools.executeRemoveUnit(unitId);
}
catch (final SQLException e) {
logDBWarning(e, "(will continue with next)");
continue;
}
}
tools.connection.commit();
}
}
}
catch (final SQLException e) {
onDbToolsError(e);
}
catch (final IOException e) {
onDbToolsError(e);
}
finally {
this.lock.writeLock().unlock();
}
}
private CompositeFrame getFrame(final Proj proj, final IProject project,
Connection connection, final IProgressMonitor monitor) throws SQLException, CoreException {
CompositeFrame frame= this.elementsList.get(proj);
if (frame == null && project.isOpen()) {
final HashMap<String, RUnitElement> elements= new HashMap<>();
frame= new CompositeFrame(this.lock, proj.getPkgName(), project.getName(), elements);
this.elementsList.put(proj, frame);
if (this.dbInitialized == 1) {
Connection newConnection= null;
PreparedStatement statement= null;
try {
if (connection == null) {
connection= newConnection= this.dbConnectionPool.getConnection();
}
statement= connection.prepareStatement(RIndex.Exports.OP_getAll_ofProject);
statement.setInt(1, proj.id);
final ResultSet result= statement.executeQuery();
while (result.next()) {
ISourceUnit su= null;
try {
final String unitId= result.getString(1);
final String modelTypeId= this.modelId2Type.get(result.getInt(2));
assert (unitId != null && modelTypeId != null);
su= this.sourceUnitManager.getSourceUnit(modelTypeId,
LTK.PERSISTENCE_CONTEXT, unitId, true, monitor );
if (su instanceof IRSourceUnit) {
final InputStream inputStream= result.getBlob(3).getBinaryStream();
final RUnitElement unitElement= RUnitElement.read((IRSourceUnit) su, frame, inputStream);
elements.put(su.getId(), unitElement);
}
}
catch (final IOException e) {
onDbReadError(e);
}
catch (final ClassNotFoundException e) {
onDbReadError(e);
}
finally {
if (su != null) {
su.disconnect(monitor);
}
}
}
}
finally {
if (newConnection != null) {
try {
newConnection.close();
} catch (final SQLException ignore) {}
}
else if (statement != null) {
try {
statement.close();
} catch (final SQLException ignore) {}
}
}
}
}
return frame;
}
/**
* Required write lock
* @param projectName
* @return
* @throws SQLException
*/
private int getOrCreateModelId(final String modelTypeId)
throws SQLException {
Integer id= this.modelType2Id.get(modelTypeId);
if (id == null) {
final DbTools tools= getDbTools();
id= Integer.valueOf(tools.addModel(modelTypeId));
tools.connection.commit();
this.modelType2Id.put(modelTypeId, id);
this.modelId2Type.put(id, modelTypeId);
}
return id.intValue();
}
/**
* Required write lock
* @param pkgData
* @param projectName
* @return
* @throws SQLException
*/
private Proj getOrCreateProjectId(final RProjectNature rProject, final RPkgData pkgData)
throws SQLException {
final IProject project= rProject.getProject();
Proj proj= this.projects.get(project.getName());
if (proj == null) {
if (!project.isOpen() || RProjectNature.getRProject(project) == null) {
return null;
}
final String name= project.getName();
final String pkgName= (pkgData != null) ? pkgData.getPkgName() : null;
final DbTools tools= getDbTools();
final int id= tools.addProj(name, pkgName);
tools.connection.commit();
proj= new Proj(id, name, pkgName);
rProject.updateRPkgConfig(pkgName);
this.projects.put(name, proj);
}
return proj;
}
private void doClearProj(final Proj proj, final List<String> modelTypeIds,
final CompositeFrame frame)
throws SQLException {
final DbTools tools= getDbTools();
for (final String modelTypeId : modelTypeIds) {
if (frame != null) {
frame.removeModelElements(modelTypeId);
}
final int modelId= getOrCreateModelId(modelTypeId);
tools.clearProj(proj.id, modelId);
}
tools.connection.commit();
}
private void doUpdatePkg(final Proj proj, final RPkgData pkgData,
final RProjectNature rProject, final CompositeFrame frame)
throws SQLException {
if (Objects.equals(proj.getPkgName(), pkgData.getPkgName())) {
return;
}
final DbTools tools= getDbTools();
proj.setPkgName(pkgData.getPkgName());
rProject.updateRPkgConfig(pkgData.getPkgName());
if (frame != null) {
this.elementsList.put(proj, new CompositeFrame(this.lock,
pkgData.getPkgName(), proj.name, frame ));
}
tools.updateProj(proj);
tools.connection.commit();
}
private void initDB() {
if (this.dbInitialized != 0) {
return;
}
this.dbInitialized= -1;
try {
final IPath location= RCorePlugin.getInstance().getStateLocation();
final File directory= location.append("db").toFile(); //$NON-NLS-1$
this.dbConnectionPool= EmbeddedDB.createConnectionPool(directory.getAbsolutePath()); //"jdbc:derby:"
if (this.dbConnectionPool != null) {
if (checkVersion()) {
if (loadModelTypes() && checkProjects()) {
this.dbInitialized= 1;
}
}
}
}
catch (final Exception e) {
this.dbInitialized= -1;
RCorePlugin.log(new Status(IStatus.ERROR, RCore.BUNDLE_ID, -1, "An error occurred when initializing DB for model.", e));
}
}
private boolean checkVersion() {
Connection connection= null;
try {
connection= this.dbConnectionPool.getConnection();
connection.setAutoCommit(false);
final ResultSet schemas= connection.getMetaData().getSchemas();
boolean schemaExists= false;
while (schemas.next()) {
if (RIndex.NAME.equals(schemas.getString(1))) {
schemaExists= true;
break;
}
}
if (schemaExists) {
try (final Statement statement= connection.createStatement()) {
final ResultSet result= statement.executeQuery("select VALUE from RINDEX.PROPERTIES where (NAME= 'version')");
if (result.next() && RIndex.VERSION.equals(result.getString(1))) {
return true;
}
}
catch (final SQLException ignore) {}
final List<String> names= new ArrayList<>();
final ResultSet tables= connection.getMetaData().getTables(null, RIndex.NAME, null, new String[] { "TABLE" }); //$NON-NLS-1$
while (tables.next()) {
final String tableName= tables.getString("TABLE_NAME"); //$NON-NLS-1$
if (tableName != null) {
names.add(tableName);
}
}
// Dependencies
if (names.remove(RIndex.NamesIdx.NAME)) {
names.add(RIndex.NamesIdx.NAME);
}
if (names.remove(RIndex.SourceUnits.NAME)) {
names.add(RIndex.SourceUnits.NAME);
}
if (names.remove(RIndex.Projects.NAME)) {
names.add(RIndex.Projects.NAME);
}
if (names.remove(RIndex.Models.NAME)) {
names.add(RIndex.Models.NAME);
}
try (final Statement statement= connection.createStatement()) {
for (final String name : names) {
statement.execute("drop table " + RIndex.NAME + '.' + name); //$NON-NLS-1$
}
}
}
try (final Statement statement= connection.createStatement()) {
statement.execute(RIndex.Properties.DEFINE_1);
statement.execute(RIndex.Models.DEFINE_1);
statement.execute(RIndex.Projects.DEFINE_1);
statement.execute(RIndex.SourceUnits.DEFINE_1);
statement.execute(RIndex.NamesIdx.DEFINE_1);
statement.execute(RIndex.MainNames.DEFINE_1);
statement.execute(RIndex.Exports.DEFINE_1);
statement.execute("insert into " + RIndex.Properties.QNAME + " (NAME, VALUE) values ('version', '" + RIndex.VERSION + "')");
}
connection.commit();
return true;
}
catch (final SQLException e) {
onDbToolsError(e);
return false;
}
finally {
if (connection != null) {
try {
connection.close();
} catch (final SQLException ignore) {}
}
}
}
private boolean loadModelTypes() {
try {
this.modelType2Id.clear();
this.modelId2Type.clear();
final DbTools tools= getDbTools();
try (final Statement statement= tools.connection.createStatement()) {
final ResultSet result= statement.executeQuery(RIndex.Models.OP_getAll);
while (result.next()) {
final String modelTypeId= result.getString(1).intern();
final int id= result.getInt(2);
this.modelType2Id.put(modelTypeId, Integer.valueOf(id));
this.modelId2Type.put(id, modelTypeId);
}
return true;
}
}
catch (final SQLException e) {
this.modelType2Id.clear();
this.modelId2Type.clear();
onDbToolsError(e);
return false;
}
}
private boolean checkProjects() {
try {
final IWorkspaceRoot root= ResourcesPlugin.getWorkspace().getRoot();
final DbTools tools= getDbTools();
try (final Statement statement= tools.connection.createStatement()) {
final ResultSet result= statement.executeQuery(RIndex.Projects.OP_getAll);
while (result.next()) {
final int id= result.getInt(1);
final String name= result.getString(2).intern();
final String pkgName= result.getString(3);
final IProject project= root.getProject(name);
if (project != null && project.isOpen()) {
final Proj proj= new Proj(id, name, pkgName);
this.projects.put(name, proj);
}
else {
try {
tools.removeProj(id);
tools.connection.commit();
}
catch (final SQLException e) {
logDBWarning(e, "(will continue with next)");
}
}
}
return true;
}
}
catch (final SQLException e) {
this.projects.clear();
onDbToolsError(e);
return false;
}
}
private void removeProject(final String projectName) {
final Proj proj= this.projects.remove(projectName);
if (proj != null) {
proj.removed= true;
if (this.dbInitialized == 1) {
try {
final DbTools tools= getDbTools();
tools.removeProj(proj.id);
tools.connection.commit();
}
catch (final SQLException e) {
onDbToolsError(e);
}
}
}
}
public void updateProjectConfigRemoved(final IProject project) {
this.lock.writeLock().lock();
try {
removeProject(project.getName());
}
finally {
this.lock.writeLock().unlock();
}
}
public void updateProjectConfigClosed(final IProject project) {
this.lock.writeLock().lock();
try {
removeProject(project.getName());
}
finally {
this.lock.writeLock().unlock();
}
}
private static void logDBWarning(final Exception e, final String info) {
RCorePlugin.log(new Status(IStatus.ERROR, RCore.BUNDLE_ID, -1, "An error occurred when replicate model to DB. " + info, e));
}
private DbTools getDbTools() throws SQLException {
if (this.dbTools == null) {
if (this.dbInitialized > 1) {
throw new SQLException("DB is closed.");
}
this.dbTools= new DbTools(this.dbConnectionPool.getConnection());
}
return this.dbTools;
}
private void onDbToolsError(final Exception e) {
RCorePlugin.log(new Status(IStatus.ERROR, RCore.BUNDLE_ID, -1, "An error occurred when replicate model to DB.", e));
if (this.dbTools != null) {
try {
this.dbTools.connection.close();
this.dbTools= null;
}
catch (final SQLException ignore) {}
}
}
private void closeDbTools() {
if (this.dbTools != null) {
try {
this.dbTools.connection.close();
this.dbTools= null;
}
catch (final SQLException e) {
onDbToolsError(e);
}
}
}
private void onDbReadError(final Exception e) throws CoreException {
if (e instanceof SQLException) {
if ("08000".equals(((SQLException) e).getSQLState())) { //$NON-NLS-1$
RCorePlugin.log(new Status(IStatus.WARNING, RCore.BUNDLE_ID, -1, "Thread was interrupted when searching index in DB.", e));
throw new CoreException(Status.CANCEL_STATUS);
}
}
RCorePlugin.log(new Status(IStatus.ERROR, RCore.BUNDLE_ID, -1, "An error occurred when searching index in DB.", e));
}
public IRFrame getProjectFrame(final RProject rProject) throws CoreException {
final Proj proj= this.projects.get(rProject.getProject().getName());
if (proj != null) {
this.lock.readLock().lock();
try {
return getFrame(proj, rProject.getProject(), null, null);
}
catch (final SQLException e) {
onDbReadError(e);
}
finally {
this.lock.readLock().unlock();
}
}
return null;
}
public Set<String> getPkgNames() {
final Set<String> names= new HashSet<>(this.projects.size());
for (final Proj proj : this.projects.values()) {
final String pkgName= proj.getPkgName();
if (pkgName != null) {
names.add(pkgName);
}
}
return names;
}
public String getPkgName(final String projectName) {
final Proj proj= this.projects.get(projectName);
if (proj != null) {
return proj.getPkgName();
}
return null;
}
public String getPkgProject(final String pkgName) throws CoreException {
for (final Proj proj : this.projects.values()) {
if (pkgName.equals(proj.getPkgName())) {
return proj.name;
}
}
return null;
}
public List<ISourceUnit> findReferencingSourceUnits(final RProject rProject, final RElementName name,
final IProgressMonitor monitor) throws CoreException {
if (name.getNextSegment() != null || name.getType() != RElementName.MAIN_DEFAULT || name.getSegmentName() == null) {
throw new UnsupportedOperationException("Only common top level names are supported.");
}
final SubMonitor progress= SubMonitor.convert(monitor);
final ISourceUnitManager suManager= LTK.getSourceUnitManager();
final List<ISourceUnit> matches= new ArrayList<>();
final Proj proj= this.projects.get(rProject.getProject().getName());
this.lock.readLock().lock();
Connection connection= null;
try {
if (proj == null || proj.isRemoved()
|| this.dbInitialized != 1) {
return null;
}
connection= this.dbConnectionPool.getConnection();
final PreparedStatement statement= connection.prepareStatement(RIndex.MainNames.OP_findSourceUnits_ofProjectAndName);
statement.setInt(1, proj.id);
statement.setString(2, name.getSegmentName());
final ResultSet result= statement.executeQuery();
while (result.next()) {
final String unitId= result.getString(1);
final String modelTypeId= this.modelId2Type.get(result.getInt(2));
assert (unitId != null && modelTypeId != null);
final ISourceUnit su= loadSourceUnit(suManager, unitId, modelTypeId, progress);
if (su != null) {
matches.add(su);
}
}
return matches;
}
catch (final SQLException e) {
onDbReadError(e);
closeSourceUnits(matches, progress);
return null;
}
finally {
this.lock.readLock().unlock();
if (connection != null) {
try {
connection.close();
} catch (final SQLException ignore) {}
}
}
}
private ISourceUnit loadSourceUnit(final ISourceUnitManager manager,
final String sourceUnitId, final String modelTypeId, final SubMonitor progress) {
try {
return manager.getSourceUnit(modelTypeId, LTK.PERSISTENCE_CONTEXT, sourceUnitId, true, progress);
}
catch (final Exception e) {
RCorePlugin.log(new Status(IStatus.ERROR, RCore.BUNDLE_ID, 0,
String.format("An error occurred when restoring source unit '%1$s' for model '%2$s'.",
sourceUnitId, modelTypeId ),
e ));
return null;
}
}
private void closeSourceUnits(final List<ISourceUnit> sourceUnits,
final SubMonitor progress) {
for (final ISourceUnit su : sourceUnits) {
su.disconnect(progress);
}
}
}