/******************************************************************************* * 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.SQLException; import java.util.ArrayList; import java.util.List; import com.buildml.model.FatalBuildStoreError; import com.buildml.model.IActionMgr; import com.buildml.model.IBuildStore; import com.buildml.model.IFileMgr; import com.buildml.model.IPackageMemberMgr; import com.buildml.model.IPackageMemberMgr.PackageDesc; import com.buildml.model.IPackageMgr; import com.buildml.model.IPackageMgrListener; import com.buildml.model.IPackageRootMgr; import com.buildml.model.ISlotTypes; import com.buildml.model.ISlotTypes.SlotDetails; import com.buildml.model.types.FileSet; import com.buildml.model.types.ActionSet; import com.buildml.utils.errors.ErrorCode; import com.buildml.utils.errors.FatalError; /** * A manager class (that supports the BuildStore class) responsible for managing all * BuildStore information pertaining to packages. * <p> * There should be exactly one PackageMgr object per BuildStore object. Use the * BuildStore's getPackageMgr() method to obtain that one instance. * * @author "Peter Smith <psmith@arapiki.com>" */ /* package private */ class PackageMgr implements IPackageMgr { /*=====================================================================================* * FIELDS/TYPES *=====================================================================================*/ /** ID number for the <import> package */ private final static int IMPORT_PACKAGE_ID = 0; /** ID number for the "Root" folder */ private final static int ROOT_FOLDER_ID = 1; /** The BuildStore that owns this package manager */ private IBuildStore buildStore; /** * Our database manager object, used to access the database content. This is provided * to us when the Packages object is first instantiated. */ private BuildStoreDB db = null; /** The FileMgr object that manages the files in our packages. */ private IFileMgr fileMgr = null; /** The ActionMgr object that manages the actions in our packages. */ private IActionMgr actionMgr = null; /** The PackageMemberMgr object that manages the members of this package. */ private IPackageMemberMgr pkgMemberMgr = null; /** The SlotMgr object that manages the slots in our packages. */ private SlotMgr slotMgr = null; /** * Various prepared statements for database access. */ private PreparedStatement addPackagePrepStmt = null, findPackageByNamePrepStmt = null, findPackageByIdPrepStmt = null, findPackageTypePrepStmt = null, findPackageParentPrepStmt = null, updatePackageParentPrepStmt = null, updatePackageNamePrepStmt = null, findAllPackagesPrepStmt = null, findChildPackagesPrepStmt = null, removePackageByIdPrepStmt = null, insertExportPrepStmt = null, findExportPrepStmt = null, removeExportPrepStmt = null; /** The event listeners who are registered to learn about package changes */ List<IPackageMgrListener> listeners = new ArrayList<IPackageMgrListener>(); /** * The package ID of "Main". This is a variable (not a constant), since it didn't * exist in databases < versions 404. */ private int mainPackageId = -1; /*=====================================================================================* * CONSTRUCTORS *=====================================================================================*/ /** * Create a new Packages object, which represents the file/action packages that * are part of the BuildStore. * * @param buildStore The BuildStore that this Packages object belongs to. */ public PackageMgr(BuildStore buildStore) { this.buildStore = buildStore; this.db = buildStore.getBuildStoreDB(); this.fileMgr = buildStore.getFileMgr(); this.actionMgr = buildStore.getActionMgr(); this.slotMgr = buildStore.getSlotMgr(); /* initialize prepared database statements */ addPackagePrepStmt = db.prepareStatement("insert into packages values (null, ?, " + ROOT_FOLDER_ID + ", ?)"); findPackageByNamePrepStmt = db.prepareStatement("select id from packages where name = ?"); findPackageByIdPrepStmt = db.prepareStatement("select name from packages where id = ?"); findPackageTypePrepStmt = db.prepareStatement("select isFolder from packages where id = ?"); findPackageParentPrepStmt = db.prepareStatement("select parent from packages where id = ?"); updatePackageParentPrepStmt = db.prepareStatement("update packages set parent = ? where id = ?"); updatePackageNamePrepStmt = db.prepareStatement("update packages set name = ? where id = ?"); findAllPackagesPrepStmt = db.prepareStatement( "select name from packages where isFolder = 0 order by name collate nocase"); findChildPackagesPrepStmt = db.prepareStatement( "select id from packages where parent = ? and id != " + ROOT_FOLDER_ID + " order by isFolder desc, name collate nocase"); removePackageByIdPrepStmt = db.prepareStatement("delete from packages where id = ?"); insertExportPrepStmt = db.prepareStatement("insert into pkgExports values (?, ?)"); findExportPrepStmt = db.prepareStatement("select fileGroupId from pkgExports where slotId = ?"); removeExportPrepStmt = db.prepareStatement("delete from pkgExports where slotId = ?"); } /*=====================================================================================* * PACKAGE-PRIVATE METHODS *=====================================================================================*/ /** * A second phase of initialization, needed when managers aren't fully initialized * in the necessary order. */ public void initPass2() { this.pkgMemberMgr = buildStore.getPackageMemberMgr(); } /*=====================================================================================* * PUBLIC METHODS *=====================================================================================*/ /* (non-Javadoc) * @see com.buildml.model.IPackageMgr#getRootFolder() */ @Override public int getRootFolder() { return ROOT_FOLDER_ID; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.IPackageMgr#getImportPackage() */ @Override public int getImportPackage() { return IMPORT_PACKAGE_ID; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.IPackageMgr#getImportPackage() */ @Override public int getMainPackage() { if (mainPackageId == -1) { mainPackageId = getId("Main"); } return mainPackageId; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.IPackageMgr#addPackage(java.lang.String) */ @Override public int addPackage(String packageName) { /* add the new package */ int pkgId = addPackageOrFolderHelper(packageName, false); if (pkgId < 0) { return pkgId; } /* set the package's roots to the same level as the workspace root */ IPackageRootMgr pkgRootMgr = buildStore.getPackageRootMgr(); int workspaceRootId = pkgRootMgr.getWorkspaceRoot(); if (workspaceRootId == ErrorCode.NOT_FOUND) { throw new FatalBuildStoreError( "Workspace root must be set before addPackage is called"); } int rc = pkgRootMgr.setPackageRoot(pkgId, IPackageRootMgr.SOURCE_ROOT, workspaceRootId); if (rc != ErrorCode.OK) { return rc; } rc = pkgRootMgr.setPackageRoot(pkgId, IPackageRootMgr.GENERATED_ROOT, workspaceRootId); if (rc != ErrorCode.OK) { return rc; } return pkgId; }; /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.IPackageMgr#addFolder(java.lang.String) */ @Override public int addFolder(String folderName) { return addPackageOrFolderHelper(folderName, true); } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.IPackageMgr#getPackageName(int) */ @Override public String getName(int folderOrPackageId) { /* find the package in our table */ String results[] = null; try { findPackageByIdPrepStmt.setInt(1, folderOrPackageId); results = db.executePrepSelectStringColumn(findPackageByIdPrepStmt); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } /* no result == no package with this Id */ if (results.length == 0) { return null; } /* one result == we have the correct name */ else if (results.length == 1) { return results[0]; } /* multiple results is an error */ else { throw new FatalBuildStoreError("Multiple entries found in packages table, for ID " + folderOrPackageId); } }; /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.IPackageMgr#setName(int, java.lang.String) */ @Override public int setName(int folderOrPackageId, String newName) { /* if there's no change in name, there's nothing to update */ if (newName.equals(getName(folderOrPackageId))) { return ErrorCode.OK; } /* check that the package/folder doesn't already exist in the database */ if (getId(newName) != ErrorCode.NOT_FOUND){ return ErrorCode.ALREADY_USED; } /* validate the new package/folder's name */ if (!isValidName(newName)){ return ErrorCode.INVALID_NAME; } /* * If this is a package (not a folder), we also must rename the associated * roots. This is done by deleting the old roots and adding new roots. */ IPackageRootMgr pkgRootMgr = buildStore.getPackageRootMgr(); boolean isFolder = isFolder(folderOrPackageId); int srcRootPath = 0; int genRootPath = 0; if (!isFolder) { srcRootPath = pkgRootMgr.getPackageRoot(folderOrPackageId, IPackageRootMgr.SOURCE_ROOT); genRootPath = pkgRootMgr.getPackageRoot(folderOrPackageId, IPackageRootMgr.GENERATED_ROOT); pkgRootMgr.removePackageRoot(folderOrPackageId, IPackageRootMgr.SOURCE_ROOT); pkgRootMgr.removePackageRoot(folderOrPackageId, IPackageRootMgr.GENERATED_ROOT); } /* update the database */ try { updatePackageNamePrepStmt.setString(1, newName); updatePackageNamePrepStmt.setInt(2, folderOrPackageId); int rowCount = db.executePrepUpdate(updatePackageNamePrepStmt); if (rowCount == 0) { return ErrorCode.NOT_FOUND; } } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } /* now, add the new package roots at the same location as the old */ if (!isFolder){ pkgRootMgr.setPackageRoot(folderOrPackageId, IPackageRootMgr.SOURCE_ROOT, srcRootPath); pkgRootMgr.setPackageRoot(folderOrPackageId, IPackageRootMgr.GENERATED_ROOT, genRootPath); } /* notify anybody who cares that our name has changed */ notifyListeners(folderOrPackageId, IPackageMgrListener.CHANGED_NAME); return ErrorCode.OK; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.IPackageMgr#getPackageId(java.lang.String) */ @Override public int getId(String folderOrPackageName) { /* find the package into our table */ Integer results[] = null; try { findPackageByNamePrepStmt.setString(1, folderOrPackageName); results = db.executePrepSelectIntegerColumn(findPackageByNamePrepStmt); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } /* no result == no package by this name */ if (results.length == 0) { return ErrorCode.NOT_FOUND; } /* one result == we have the correct ID */ else if (results.length == 1) { return results[0]; } /* multiple results is an error */ else { throw new FatalBuildStoreError("Multiple entries found in packages table, for name " + folderOrPackageName); } } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.IPackageMgr#removePackage(java.lang.String) */ @Override public int remove(int folderOrPackageId) { IPackageMemberMgr pkgMemberMgr = buildStore.getPackageMemberMgr(); /* we can't remove the "<import>" package or the "Root" folder */ if ((folderOrPackageId == ROOT_FOLDER_ID) || (folderOrPackageId == IMPORT_PACKAGE_ID) || (folderOrPackageId == getMainPackage())) { return ErrorCode.CANT_REMOVE; } /* determine if this package is used by any files */ FileSet filesInPackage = pkgMemberMgr.getFilesInPackage(folderOrPackageId); if (filesInPackage.size() != 0) { return ErrorCode.CANT_REMOVE; } /* determine if this package is used by any actions */ ActionSet actionsInPackage = pkgMemberMgr.getActionsInPackage(folderOrPackageId); if (actionsInPackage.size() != 0) { return ErrorCode.CANT_REMOVE; } /* determine if the folder has any children */ if (getFolderChildren(folderOrPackageId).length != 0) { return ErrorCode.CANT_REMOVE; } /* remove any associated package roots */ if (!isFolder(folderOrPackageId)) { IPackageRootMgr pkgRootMgr = buildStore.getPackageRootMgr(); int rc = pkgRootMgr.removePackageRoot(folderOrPackageId, IPackageRootMgr.SOURCE_ROOT); if (rc != ErrorCode.OK) { return rc; } rc = pkgRootMgr.removePackageRoot(folderOrPackageId, IPackageRootMgr.GENERATED_ROOT); if (rc != ErrorCode.OK) { return rc; } } /* remove from the database */ int removedCount = 0; try { removePackageByIdPrepStmt.setInt(1, folderOrPackageId); removedCount = db.executePrepUpdate(removePackageByIdPrepStmt); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } if (removedCount == 0) { return ErrorCode.NOT_FOUND; } notifyListeners(folderOrPackageId, IPackageMgrListener.REMOVED_PACKAGE); return ErrorCode.OK; }; /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.IPackageMgr#getPackages() */ @Override public String[] getPackages() { /* find all the package into our table */ return db.executePrepSelectStringColumn(findAllPackagesPrepStmt); }; /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.IPackageMgr#getFolderChildren(int) */ @Override public Integer[] getFolderChildren(int folderId) { try { findChildPackagesPrepStmt.setInt(1, folderId); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } return db.executePrepSelectIntegerColumn(findChildPackagesPrepStmt); } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.IPackageMgr#getParent(int) */ @Override public int getParent(int folderOrPackageId) { Integer results[] = null; try { findPackageParentPrepStmt.setInt(1, folderOrPackageId); results = db.executePrepSelectIntegerColumn(findPackageParentPrepStmt); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } /* If folderOrPackageId is invalid (or duplicated), assume not a folder */ if (results.length != 1) { return ErrorCode.NOT_FOUND; } return results[0]; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.IPackageMgr#setParent(int, int) */ @Override public int setParent(int folderOrPackageId, int parentId) { /* validate that the parent and child are valid */ if (!isValid(parentId) || !isValid(folderOrPackageId)) { return ErrorCode.BAD_VALUE; } /* if there's no change in parent, there's no need to update */ int existingParent = getParent(folderOrPackageId); if (existingParent == ErrorCode.NOT_FOUND) { return ErrorCode.BAD_VALUE; } if (existingParent == parentId) { return ErrorCode.OK; } /* the parent must be a folder */ if (!isFolder(parentId)) { return ErrorCode.NOT_A_DIRECTORY; } /* we can't move the root folder or the <import> or main packages */ if ((folderOrPackageId == ROOT_FOLDER_ID) || (folderOrPackageId == IMPORT_PACKAGE_ID) || (folderOrPackageId == getMainPackage())) { return ErrorCode.BAD_PATH; } /* if the child is a folder, make sure it doesn't create a cycle */ if (isFolder(folderOrPackageId)) { /* * Starting with the new (proposed) parent, work upward to see if the child is already * a parent. */ int ancestor = parentId; while (true) { /* a cycle has been detected */ if (ancestor == folderOrPackageId) { return ErrorCode.BAD_PATH; } /* did we reach the root, without a cycle? */ int nextAncestor = getParent(ancestor); if (nextAncestor == ancestor) { break; } ancestor = nextAncestor; } } /* update the database entry */ try { updatePackageParentPrepStmt.setInt(1, parentId); updatePackageParentPrepStmt.setInt(2, folderOrPackageId); db.executePrepUpdate(updatePackageParentPrepStmt); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } /* notify interested listeners */ notifyListeners(folderOrPackageId, IPackageMgrListener.REPARENT_PACKAGE); return ErrorCode.OK; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.IPackageMgr#isFolder(int) */ @Override public boolean isFolder(int folderOrPackageId) { Integer results[] = null; try { findPackageTypePrepStmt.setInt(1, folderOrPackageId); results = db.executePrepSelectIntegerColumn(findPackageTypePrepStmt); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } /* If folderOrPackageId is invalid (or duplicated), assume not a folder */ if (results.length != 1) { return false; } return (results[0] == 1); } /*-------------------------------------------------------------------------------------*/ /* * (non-Javadoc) * @see com.buildml.model.IPackageMgr#isValid(int) */ @Override public boolean isValid(int folderOrPackageId) { /* the ID is valid if we can find its parent */ return getParent(folderOrPackageId) != ErrorCode.NOT_FOUND; } /*-------------------------------------------------------------------------------------*/ /** * Implementation of newSlot() for packages. */ @Override public int newSlot(int typeId, String slotName, String slotDescr, int slotType, int slotPos, int slotCard, Object defaultValue, String[] enumValues) { /* check for invalid typeId, since SlotMgr can't determine this */ if (isFolder(typeId) || !isValid(typeId)) { return ErrorCode.NOT_FOUND; } /* we can't add input slots to a package */ if (slotPos == ISlotTypes.SLOT_POS_INPUT) { return ErrorCode.INVALID_OP; } /* delegate the rest of the work to SlotMgr */ int result = slotMgr.newSlot(ISlotTypes.SLOT_OWNER_PACKAGE, typeId, slotName, slotDescr, slotType, slotPos, slotCard, defaultValue, enumValues); if (result >= 0) { notifyListeners(typeId, IPackageMgrListener.CHANGED_SLOT); } return result; } /*-------------------------------------------------------------------------------------*/ /** * Implementation of changeSlot() for packages. */ @Override public int changeSlot(SlotDetails details) { int result = slotMgr.changeSlot(details); if (result == ErrorCode.OK) { notifyListeners(details.ownerId, IPackageMgrListener.CHANGED_SLOT); } return result; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.IPackageMgr#getSlots(int, int) */ @Override public SlotDetails[] getSlots(int pkgId, int slotPos) { /* validate all inputs */ if (isFolder(pkgId) || !isValid(pkgId)) { return null; } if ((slotPos < ISlotTypes.SLOT_POS_ANY) || (slotPos > ISlotTypes.SLOT_POS_LOCAL)){ return null; } /* delegate to SlotMgr */ return slotMgr.getSlots(ISlotTypes.SLOT_OWNER_PACKAGE, pkgId, slotPos); } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.IPackageMgr#getSlotByID(int) */ @Override public SlotDetails getSlotByID(int slotId) { return slotMgr.getSlotByID(slotId); } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.IPackageMgr#getSlotByName(int, java.lang.String) */ @Override public SlotDetails getSlotByName(int pkgId, String slotName) { /* all work can be delegated */ return slotMgr.getSlotByName(ISlotTypes.SLOT_OWNER_PACKAGE, pkgId, slotName); } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.IPackageMgr#trashSlot(int) */ @Override public int trashSlot(int slotId) { /* get the slot's details - especially it's owning package */ SlotDetails details = getSlotByID(slotId); if (details == null) { return ErrorCode.NOT_FOUND; } /* the rest of work can be delegated */ int result = slotMgr.trashSlot(slotId); if (result == ErrorCode.OK) { notifyListeners(details.ownerId, IPackageMgrListener.CHANGED_SLOT); } return result; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.IPackageMgr#reviveSlot(int) */ @Override public int reviveSlot(int slotId) { /* much of the work can be delegated */ int result = slotMgr.reviveSlot(slotId); /* on success, send a notification about this package */ if (result == ErrorCode.OK){ SlotDetails details = getSlotByID(slotId); if (details != null) { notifyListeners(details.ownerId, IPackageMgrListener.CHANGED_SLOT); } } return result; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.IPackageMgr#exportFileGroupToSlot(int, int) */ @Override public int exportFileGroupToSlot(int fileGroupId, int slotId) { /* * Test fileGroupId for validity, and fetch the packageId. On failure, return * ErrorCode.BAD_VALUE. */ PackageDesc desc = pkgMemberMgr.getPackageOfMember(IPackageMemberMgr.TYPE_FILE_GROUP, fileGroupId); if (desc == null) { return ErrorCode.BAD_VALUE; } /* * Test slotId for validity, check that it's an output slot for the package * containing the file group. On failure, return ErrorCode.NOT_FOUND. */ SlotDetails slotDetails = slotMgr.getSlotByID(slotId); if ((slotDetails == null) || (slotDetails.slotPos != ISlotTypes.SLOT_POS_OUTPUT) || (slotDetails.ownerType != ISlotTypes.SLOT_OWNER_PACKAGE) || (slotDetails.ownerId != desc.pkgId)) { return ErrorCode.NOT_FOUND; } /* * Check if the output slot already has a value in it. If so, return * ErrorCode.ONLY_ONE_ALLOWED. */ if (getExportedFileGroup(slotId) != ErrorCode.BAD_VALUE) { return ErrorCode.ONLY_ONE_ALLOWED; } /* update the database */ try { insertExportPrepStmt.setInt(1, fileGroupId); insertExportPrepStmt.setInt(2, slotId); db.executePrepUpdate(insertExportPrepStmt); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } return ErrorCode.OK; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.IPackageMgr#getExportedFileGroup(int) */ @Override public int getExportedFileGroup(int slotId) { /* ensure that slotId is valid, and is an output slot */ SlotDetails slotDetails = slotMgr.getSlotByID(slotId); if ((slotDetails == null) || (slotDetails.slotPos != ISlotTypes.SLOT_POS_OUTPUT) || (slotDetails.ownerType != ISlotTypes.SLOT_OWNER_PACKAGE)) { return ErrorCode.NOT_FOUND; } /* look up the file group, given the slotID */ Integer result[] = null; try { findExportPrepStmt.setInt(1, slotId); result = db.executePrepSelectIntegerColumn(findExportPrepStmt); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } /* no result => the slot exists, but has no value right now */ if (result.length == 0) { return ErrorCode.BAD_VALUE; } /* happy path - one result */ else if (result.length == 1) { return result[0].intValue(); } /* error case */ else { /* * Handle case where multiple results exist. This case will only make * sense when variants are supported. */ throw new FatalError("Can't have multiple file groups in a single slot"); } } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.IPackageMgr#removeExport(int) */ @Override public int removeExport(int slotId) { /* validate the slotId is a valid output slot */ SlotDetails slotDetails = slotMgr.getSlotByID(slotId); if ((slotDetails == null) || (slotDetails.slotPos != ISlotTypes.SLOT_POS_OUTPUT) || (slotDetails.ownerType != ISlotTypes.SLOT_OWNER_PACKAGE)) { return ErrorCode.NOT_FOUND; } /* update the database */ int rowCount = 0; try { removeExportPrepStmt.setInt(1, slotId); rowCount = db.executePrepUpdate(removeExportPrepStmt); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } /* to be successful, we must have removed a row */ return (rowCount == 0) ? ErrorCode.BAD_VALUE : ErrorCode.OK; } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.IPackageMgr#addListener(com.buildml.model.IPackageMgrListener) */ @Override public void addListener(IPackageMgrListener listener) { listeners.add(listener); } /*-------------------------------------------------------------------------------------*/ /* (non-Javadoc) * @see com.buildml.model.IPackageMgr#removeListener(com.buildml.model.IPackageMgrListener) */ @Override public void removeListener(IPackageMgrListener listener) { listeners.remove(listener); }; /*=====================================================================================* * PRIVATE METHODS *=====================================================================================*/ /** * Validate a package's name. Valid characters are digits, letters (upper or lower case), * '-' and '_'. No other characters are permitted. Package names must contain at least * three characters and start with a letter. * * @param packageName The package name to be validated. * @return True if the name is valid, else false. */ private boolean isValidName(String packageName) { if (packageName == null) { return false; } int length = packageName.length(); if (length < 3) { return false; } int i = 0; while (i != length) { char ch = packageName.charAt(i); /* first character must be a letter */ if (i == 0) { if (!(Character.isLetter(ch))) { return false; } } /* following characters are letter, digit, _ or - */ else { if (!(Character.isLetterOrDigit(ch) || (ch == '_') || (ch == '-'))){ return false; } } i++; } return true; } /*-------------------------------------------------------------------------------------*/ /** * Helper method for adding a new package or folder to the database. This is called by * addPackage() or addFolder(), both which share the same name space. * * @param name Name of the new package or folder. * @param isFolder True if a folder should be created, else false for a package. * @return On success, return the package/folder's numeric ID. On failure, * return: * ErrorCode.ALREADY_USED - The name is already in use. * ErrorCode.INVALID_NAME - The name doesn't conform to naming standards. */ private int addPackageOrFolderHelper(String name, boolean isFolder) { /* check that the package/folder doesn't already exist in the database */ if (getId(name) != ErrorCode.NOT_FOUND){ return ErrorCode.ALREADY_USED; } /* validate the new package/folder's name */ if (!isValidName(name)){ return ErrorCode.INVALID_NAME; } /* insert the package into our table */ try { addPackagePrepStmt.setInt(1, isFolder ? 1 : 0); addPackagePrepStmt.setString(2, name); db.executePrepUpdate(addPackagePrepStmt); } catch (SQLException e) { throw new FatalBuildStoreError("Unable to execute SQL statement", e); } /* return the new package's ID number */ int pkgId = db.getLastRowID(); notifyListeners(pkgId, IPackageMgrListener.ADDED_PACKAGE); return pkgId; } /*-------------------------------------------------------------------------------------*/ /** * Notify any registered listeners about our change in state. * @param pkgId The package that has changed. * @param how The way in which the package changed (see {@link IPackageMgrListener}). */ private void notifyListeners(int pkgId, int how) { /* * Make a copy of the listeners list, otherwise a registered listener can't remove * itself from the list within the packageChangeNotification() method. */ IPackageMgrListener listenerCopy[] = listeners.toArray(new IPackageMgrListener[listeners.size()]); for (int i = 0; i < listenerCopy.length; i++) { listenerCopy[i].packageChangeNotification(pkgId, how); } } /*-------------------------------------------------------------------------------------*/ }