| /*=============================================================================# |
| # 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); |
| } |
| } |
| |
| } |