/*******************************************************************************
* Copyright (c) 2011 Arapiki Solutions Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* "Peter Smith <psmith@arapiki.com>" - initial API and
* implementation and/or initial documentation
*******************************************************************************/
package com.buildml.model.impl;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import com.buildml.model.FatalBuildStoreError;
import com.buildml.model.IActionMgr;
import com.buildml.model.IActionMgr.OperationType;
import com.buildml.model.IActionTypeMgr;
import com.buildml.model.IFileMgr;
import com.buildml.model.IFileMgr.PathType;
import com.buildml.model.ISlotTypes.SlotDetails;
import com.buildml.model.IPackageMemberMgr;
import com.buildml.model.IPackageMgr;
import com.buildml.model.IReportMgr;
import com.buildml.model.types.FileRecord;
import com.buildml.model.types.FileSet;
import com.buildml.model.types.PackageSet;
import com.buildml.model.types.ActionSet;
import com.buildml.utils.errors.ErrorCode;
/**
* A manager class (that supports the BuildStore class) that handles reporting of
* information from the BuildStore. These reports are able to access the database
* directly, rather than using the standard BuildStore APIs.
* <p>
* There should be exactly one ReportMgr object per BuildStore object. Use the
* BuildStore's getReportMgr() method to obtain that one instance.
*
* @author "Peter Smith <psmith@arapiki.com>"
*/
/* package private */ class ReportMgr implements IReportMgr {
/*=====================================================================================*
* TYPES/FIELDS
*=====================================================================================*/
/**
* Our database manager object, used to access the database content. This is provided
* to us when the ReportMgr object is first instantiated.
*/
private BuildStoreDB db = null;
/**
* The BuildStore managers we interact with.
*/
private IFileMgr fileMgr = null;
private IActionMgr actionMgr = null;
private IActionTypeMgr actionTypeMgr = null;
/**
* Various prepared statement for database access.
*/
private PreparedStatement
selectFileAccessCountPrepStmt = null,
selectFileIncludesCountPrepStmt = null,
selectFilesNotUsedPrepStmt = null,
selectFilesWithMatchingNamePrepStmt = null,
selectDerivedFilesPrepStmt = null,
selectInputFilesPrepStmt = null,
selectActionsAccessingFilesPrepStmt = null,
selectActionsAccessingFilesAnyPrepStmt = null,
selectFilesAccessedByActionPrepStmt = null,
selectFilesAccessedByActionAnyPrepStmt = null,
selectWriteOnlyFilesPrepStmt = null,
selectAllFilesPrepStmt = null,
selectAllActionsPrepStmt = null;
/*=====================================================================================*
* CONSTRUCTORS
*=====================================================================================*/
/**
* Create a new Reports manager object, which performs a lot of the reporting work
* on behalf of the BuildStore.
*
* @param buildStore The BuildStore than owns this Reports object.
*/
public ReportMgr(BuildStore buildStore) {
this.db = buildStore.getBuildStoreDB();
this.fileMgr = buildStore.getFileMgr();
this.actionMgr = buildStore.getActionMgr();
this.actionTypeMgr = buildStore.getActionTypeMgr();
selectFileAccessCountPrepStmt = db.prepareStatement(
"select fileId, count(*) as usage from actionFiles, files " +
"where pathType=? and (actionFiles.fileId = files.id) and (files.trashed = 0)" +
"group by fileId order by usage desc");
selectFileIncludesCountPrepStmt = db.prepareStatement(
"select fileId1, usage from fileIncludes where fileId2 = ? order by usage desc");
selectFilesNotUsedPrepStmt = db.prepareStatement("" +
"select files.id from files left join actionFiles on (files.id = actionFiles.fileId)" +
" where (files.pathType = " + PathType.TYPE_FILE.ordinal() +
") and (actionFiles.actionId is null) and (files.trashed = 0)");
selectFilesWithMatchingNamePrepStmt = db.prepareStatement(
"select files.id from files where (name like ?) and (files.trashed = 0) and " +
"(pathType = " + PathType.TYPE_FILE.ordinal() + ")");
selectDerivedFilesPrepStmt = db.prepareStatement(
"select distinct fileId from actionFiles where actionId in " +
"(select actionId from actionFiles where fileId = ? and " +
"operation = " + OperationType.OP_READ.ordinal() + ") " +
"and operation = " + OperationType.OP_WRITE.ordinal());
selectInputFilesPrepStmt = db.prepareStatement(
"select distinct fileId from actionFiles where actionId in " +
"(select actionId from actionFiles where fileId = ? and " +
"operation = " + OperationType.OP_WRITE.ordinal() + ") " +
"and operation = " + OperationType.OP_READ.ordinal());
selectActionsAccessingFilesPrepStmt = db.prepareStatement(
"select actionId from actionFiles where fileId = ? and operation = ?");
selectActionsAccessingFilesAnyPrepStmt = db.prepareStatement(
"select actionId from actionFiles where fileId = ?");
selectFilesAccessedByActionPrepStmt = db.prepareStatement(
"select fileId from actionFiles where actionId = ? and operation = ?");
selectFilesAccessedByActionAnyPrepStmt = db.prepareStatement(
"select fileId from actionFiles where actionId = ?");
selectWriteOnlyFilesPrepStmt = db.prepareStatement(
"select writeFileId from (select distinct fileId as writeFileId from " +
"actionFiles where operation = " + OperationType.OP_WRITE.ordinal() + ") " +
"left join (select distinct fileId as readFileId from actionFiles " +
"where operation = " + OperationType.OP_READ.ordinal() + ") on writeFileId = readFileId " +
"where readFileId is null");
selectAllFilesPrepStmt = db.prepareStatement("select id from files where trashed = 0");
selectAllActionsPrepStmt = db.prepareStatement("select actionId from buildActions");
}
/*=====================================================================================*
* PUBLIC METHODS
*=====================================================================================*/
/* (non-Javadoc)
* @see com.buildml.model.impl.IReportMgr#reportMostCommonlyAccessedFiles()
*/
@Override
public FileRecord[] reportMostCommonlyAccessedFiles() {
ArrayList<FileRecord> results = new ArrayList<FileRecord>();
try {
selectFileAccessCountPrepStmt.setInt(1, PathType.TYPE_FILE.ordinal());
ResultSet rs = db.executePrepSelectResultSet(selectFileAccessCountPrepStmt);
while (rs.next()) {
FileRecord record = new FileRecord(rs.getInt(1));
record.setCount(rs.getInt(2));
results.add(record);
}
rs.close();
} catch (SQLException e) {
throw new FatalBuildStoreError("Unable to execute SQL statement", e);
}
return results.toArray(new FileRecord[0]);
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.impl.IReportMgr#reportMostCommonIncludersOfFile(int)
*/
@Override
public FileRecord[] reportMostCommonIncludersOfFile(int includedFile) {
ArrayList<FileRecord> results = new ArrayList<FileRecord>();
try {
selectFileIncludesCountPrepStmt.setInt(1, includedFile);
ResultSet rs = db.executePrepSelectResultSet(selectFileIncludesCountPrepStmt);
while (rs.next()) {
FileRecord record = new FileRecord(rs.getInt(1));
record.setCount(rs.getInt(2));
results.add(record);
}
rs.close();
} catch (SQLException e) {
throw new FatalBuildStoreError("Unable to execute SQL statement", e);
}
return results.toArray(new FileRecord[0]);
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.impl.IReportMgr#reportFilesNeverAccessed()
*/
@Override
public FileSet reportFilesNeverAccessed() {
FileSet results = new FileSet(fileMgr);
try {
ResultSet rs = db.executePrepSelectResultSet(selectFilesNotUsedPrepStmt);
while (rs.next()) {
results.add(rs.getInt(1));
}
rs.close();
} catch (SQLException e) {
throw new FatalBuildStoreError("Unable to execute SQL statement", e);
}
return results;
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.impl.IReportMgr#reportFilesThatMatchName(java.lang.String)
*/
@Override
public FileSet reportFilesThatMatchName(String fileArg) {
/* map any occurrences of * into %, since that's what SQL requires */
if (fileArg != null) {
fileArg = fileArg.replace('*', '%');
}
FileSet results = new FileSet(fileMgr);
try {
selectFilesWithMatchingNamePrepStmt.setString(1, fileArg);
ResultSet rs = db.executePrepSelectResultSet(selectFilesWithMatchingNamePrepStmt);
while (rs.next()) {
results.add(rs.getInt(1));
}
rs.close();
} catch (SQLException e) {
throw new FatalBuildStoreError("Unable to execute SQL statement", e);
}
return results;
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.impl.IReportMgr#reportActionsThatMatchName(java.lang.String)
*/
@Override
public ActionSet reportActionsThatMatchName(String pattern) {
Integer results[] = actionMgr.getActionsWhereSlotIsLike(IActionMgr.COMMAND_SLOT_ID, "%" + pattern + "%");
return new ActionSet(actionMgr, results);
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.impl.IReportMgr#reportDerivedFiles(com.buildml.model.types.FileSet, boolean)
*/
@Override
public FileSet reportDerivedFiles(FileSet sourceFileSet, boolean reportIndirect) {
return reportDerivedFilesHelper(sourceFileSet, reportIndirect, selectDerivedFilesPrepStmt);
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.impl.IReportMgr#reportInputFiles(com.buildml.model.types.FileSet, boolean)
*/
@Override
public FileSet reportInputFiles(FileSet targetFileSet, boolean reportIndirect) {
return reportDerivedFilesHelper(targetFileSet, reportIndirect, selectInputFilesPrepStmt);
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.impl.IReportMgr#reportActionsThatAccessFiles(com.buildml.model.types.FileSet, com.buildml.model.IActionMgr.OperationType)
*/
@Override
public ActionSet reportActionsThatAccessFiles(FileSet fileSet,
OperationType opType) {
/* create an empty result ActionSet */
ActionSet results = new ActionSet(actionMgr);
/* for each file in the FileSet */
for (Iterator<Integer> iterator = fileSet.iterator(); iterator.hasNext();) {
int fileId = (Integer) iterator.next();
/* find the actions that access this file */
try {
ResultSet rs;
/* the case where we care about the operation type */
if (opType != OperationType.OP_UNSPECIFIED) {
selectActionsAccessingFilesPrepStmt.setInt(1, fileId);
selectActionsAccessingFilesPrepStmt.setInt(2, opType.ordinal());
rs = db.executePrepSelectResultSet(selectActionsAccessingFilesPrepStmt);
}
/* the case where we don't care */
else {
selectActionsAccessingFilesAnyPrepStmt.setInt(1, fileId);
rs = db.executePrepSelectResultSet(selectActionsAccessingFilesAnyPrepStmt);
}
/* add the results into our ActionSet */
while (rs.next()) {
int actionId = rs.getInt(1);
/* only add the result if it's not in the set */
if (!results.isMember(actionId)){
results.add(rs.getInt(1));
}
}
rs.close();
} catch (SQLException e) {
throw new FatalBuildStoreError("Unable to execute SQL statement", e);
}
}
return results;
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.IReportMgr#getActionsInDirectory(com.buildml.model.types.FileSet)
*/
@Override
public ActionSet reportActionsInDirectory(FileSet directories) {
ActionSet results = new ActionSet(actionMgr);
for (int pathId : directories) {
Integer actions[] = actionMgr.getActionsWhereSlotEquals(IActionMgr.DIRECTORY_SLOT_ID, pathId);
if (actions != null) {
for (int i = 0; i < actions.length; i++) {
results.add(actions[i]);
}
}
}
return results;
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.impl.IReportMgr#reportFilesAccessedByActions(com.buildml.model.types.ActionSet, com.buildml.model.IActionMgr.OperationType)
*/
@Override
public FileSet reportFilesAccessedByActions(ActionSet actionSet, OperationType opType) {
/* create an empty result FileSet */
FileSet results = new FileSet(fileMgr);
/* for each action in the ActionSet */
for (Iterator<Integer> iterator = actionSet.iterator(); iterator.hasNext();) {
int actionId = (Integer) iterator.next();
/* find the actions that access this file */
try {
ResultSet rs;
/* the case where we care about the operation type */
if (opType != OperationType.OP_UNSPECIFIED) {
selectFilesAccessedByActionPrepStmt.setInt(1, actionId);
selectFilesAccessedByActionPrepStmt.setInt(2, opType.ordinal());
rs = db.executePrepSelectResultSet(selectFilesAccessedByActionPrepStmt);
}
/* the case where we don't care */
else {
selectFilesAccessedByActionAnyPrepStmt.setInt(1, actionId);
rs = db.executePrepSelectResultSet(selectFilesAccessedByActionAnyPrepStmt);
}
/* add the results into our FileSet */
while (rs.next()) {
int fileId = rs.getInt(1);
/* only add the result if it's not in the set */
if (!results.isMember(fileId)){
results.add(rs.getInt(1));
}
}
rs.close();
} catch (SQLException e) {
throw new FatalBuildStoreError("Unable to execute SQL statement", e);
}
}
return results;
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.impl.IReportMgr#reportWriteOnlyFiles()
*/
@Override
public FileSet reportWriteOnlyFiles() {
FileSet results = new FileSet(fileMgr);
try {
ResultSet rs = db.executePrepSelectResultSet(selectWriteOnlyFilesPrepStmt);
while (rs.next()) {
results.add(rs.getInt(1));
}
rs.close();
} catch (SQLException e) {
throw new FatalBuildStoreError("Unable to execute SQL statement", e);
}
return results;
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.impl.IReportMgr#reportAllFiles()
*/
@Override
public FileSet reportAllFiles() {
FileSet results = new FileSet(fileMgr);
try {
ResultSet rs = db.executePrepSelectResultSet(selectAllFilesPrepStmt);
while (rs.next()) {
results.add(rs.getInt(1));
}
rs.close();
} catch (SQLException e) {
throw new FatalBuildStoreError("Unable to execute SQL statement", e);
}
return results;
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.impl.IReportMgr#reportAllActions()
*/
@Override
public ActionSet reportAllActions() {
ActionSet results = new ActionSet(actionMgr);
try {
ResultSet rs = db.executePrepSelectResultSet(selectAllActionsPrepStmt);
while (rs.next()) {
results.add(rs.getInt(1));
}
rs.close();
} catch (SQLException e) {
throw new FatalBuildStoreError("Unable to execute SQL statement", e);
}
return results;
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.impl.IReportMgr#reportFilesFromPackageSet(com.buildml.model.types.PackageSet)
*/
@Override
public FileSet reportFilesFromPackageSet(PackageSet pkgSet) {
FileSet results = new FileSet(fileMgr);
IPackageMgr pkgMgr = pkgSet.getBuildStore().getPackageMgr();
/*
* Form the (complex) query string, which considers each package/scope individually.
*/
StringBuffer sb = new StringBuffer(256);
sb.append("select memberId from packageMembers where memberType = " +
IPackageMemberMgr.TYPE_FILE + " and ");
int memberCount = 0;
String pkgList[] = pkgMgr.getPackages();
for (String pkgName : pkgList) {
int pkgId = pkgMgr.getId(pkgName);
if (pkgId != ErrorCode.NOT_FOUND) {
/* is this package in the set? */
boolean hasPrivate = pkgSet.isMember(pkgId, IPackageMemberMgr.SCOPE_PRIVATE);
boolean hasPublic = pkgSet.isMember(pkgId, IPackageMemberMgr.SCOPE_PUBLIC);
/* do we need a "or" between neighboring tests? */
if (hasPrivate || hasPublic) {
memberCount++;
if (memberCount > 1) {
sb.append(" or ");
}
}
/* form the condition for comparing the file's packages/scope */
if (hasPrivate && hasPublic) {
sb.append("(pkgId == " + pkgId + ")");
} else if (hasPrivate) {
sb.append("((pkgId == " + pkgId +
") and (scopeId == " + IPackageMemberMgr.SCOPE_PRIVATE + "))");
} else if (hasPublic) {
sb.append("((pkgId == " + pkgId +
") and (scopeId == " + IPackageMemberMgr.SCOPE_PUBLIC + "))");
}
}
}
/* if the package set was empty, so to is the result set */
if (memberCount == 0) {
return results;
}
ResultSet rs = db.executeSelectResultSet(sb.toString());
try {
while (rs.next()) {
results.add(rs.getInt(1));
}
rs.close();
} catch (SQLException e) {
throw new FatalBuildStoreError("Unable to execute SQL statement", e);
}
return results;
}
/*-------------------------------------------------------------------------------------*/
/* (non-Javadoc)
* @see com.buildml.model.impl.IReportMgr#reportActionsFromPackageSet(com.buildml.model.types.PackageSet)
*/
@Override
public ActionSet reportActionsFromPackageSet(PackageSet pkgSet) {
ActionSet results = new ActionSet(actionMgr);
IPackageMgr pkgMgr = pkgSet.getBuildStore().getPackageMgr();
/*
* Form the (complex) query string.
*/
StringBuffer sb = new StringBuffer(256);
sb.append("select memberId from packageMembers where memberType = " +
IPackageMemberMgr.TYPE_ACTION + " and ");
int memberCount = 0;
String pkgList[] = pkgMgr.getPackages();
for (String pkgName : pkgList) {
int pkgId = pkgMgr.getId(pkgName);
if (pkgId != ErrorCode.NOT_FOUND) {
/* is this package in the set? */
boolean isMember = pkgSet.isMember(pkgId, IPackageMemberMgr.SCOPE_PUBLIC);
/* do we need a "or" between neighboring tests? */
if (isMember) {
memberCount++;
if (memberCount > 1) {
sb.append(" or ");
}
/* form the condition for comparing the action's package. */
sb.append("(pkgId == " + pkgId + ")");
}
}
}
/* if the package set was empty, so too is the result set */
if (memberCount == 0) {
return results;
}
ResultSet rs = db.executeSelectResultSet(sb.toString());
try {
while (rs.next()) {
results.add(rs.getInt(1));
}
rs.close();
} catch (SQLException e) {
throw new FatalBuildStoreError("Unable to execute SQL statement", e);
}
return results;
}
/*=====================================================================================*
* PRIVATE METHODS
*=====================================================================================*/
/**
* A helper method for reportDerivedFiles and reportInputFiles that both use the same
* algorithm, but a slightly different SQL query.
*
* @param startFileSet The set of files that we're deriving from, or that are used as
* the target of the derivation.
* @param reportIndirect True if we should do multiple iterations of derivation.
* @param sqlStatement The prepared SQL statement to find derived/input files.
* @return The result FileSet.
*/
private FileSet reportDerivedFilesHelper(FileSet startFileSet, boolean reportIndirect,
PreparedStatement sqlStatement) {
/*
* Create a new empty FileSet for tracking all the results. Each time we
* iterate through the loop, we merge the results into this FileSet
*/
FileSet results = new FileSet(fileMgr);
/* the first set of files to analyze */
FileSet nextFileSet = startFileSet;
/*
* This variable is used to track the progress of finding new results.
* If it doesn't change from one iteration to the next, we stop the iterations.
*/
int lastNumberOfResults = 0;
/*
* Start the iterations. If "reportIndirect" is false, we'll only execute
* the loop once. Each time we go around the loop, we take the files
* from the previous round and treat them as the new set of start files.
*/
boolean done = false;
do {
/* empty FileSet to collect this round's set of results */
FileSet thisRoundOfResults = new FileSet(fileMgr);
/* iterate through each of the files in this round's FileSet */
for (int fileId : nextFileSet) {
/* determine the direct input/output files for this file, and add them to the result set */
try {
sqlStatement.setInt(1, fileId);
ResultSet rs = db.executePrepSelectResultSet(sqlStatement);
while (rs.next()) {
thisRoundOfResults.add(rs.getInt(1));
}
rs.close();
} catch (SQLException e) {
throw new FatalBuildStoreError("Unable to execute SQL statement", e);
}
}
/*
* Prepare to repeat the process by using the results from this iteration as
* the input to the next iteration.
*/
nextFileSet = thisRoundOfResults;
/*
* Merge this cycle's results into our final result set
*/
results.mergeSet(thisRoundOfResults);
/* are we done? Did we find any new results in this iteration? */
int thisNumberOfResults = results.size();
done = (thisNumberOfResults == lastNumberOfResults);
lastNumberOfResults = thisNumberOfResults;
} while (reportIndirect && !done);
/* return the combined set of results */
return results;
}
/*-------------------------------------------------------------------------------------*/
}