package net.certware.planning.cpn.handlers; import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; import java.util.HashMap; import java.util.Map; import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.Duration; import net.certware.core.ICertWareConstants; import net.certware.core.ui.log.CertWareLog; import net.certware.planning.cpn.cpnDsl.Allocation; import net.certware.planning.cpn.cpnDsl.Plan; import net.certware.planning.cpn.cpnDsl.Plans; import net.certware.planning.cpn.view.Activator; import net.certware.planning.cpn.view.preferences.PreferenceConstants; import net.certware.planning.mspdi.AssignmentType; import net.certware.planning.mspdi.AssignmentsType; import net.certware.planning.mspdi.ConstraintTypeType; import net.certware.planning.mspdi.DocumentRoot; import net.certware.planning.mspdi.DurationFormatType4; import net.certware.planning.mspdi.EarnedValueMethodType; import net.certware.planning.mspdi.FixedCostAccrualType; import net.certware.planning.mspdi.MspdiFactory; import net.certware.planning.mspdi.OvertimeRateFormatType1; import net.certware.planning.mspdi.ProjectType; import net.certware.planning.mspdi.ResourceType; import net.certware.planning.mspdi.ResourcesType; import net.certware.planning.mspdi.StandardRateFormatType1; import net.certware.planning.mspdi.TaskType; import net.certware.planning.mspdi.TasksType; import net.certware.planning.mspdi.TypeType2; import net.certware.planning.mspdi.TypeType3; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; import org.eclipse.emf.ecore.xmi.XMLResource; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.ui.actions.WorkspaceModifyOperation; /** * Translate CPN planning tasks and resources into Microsoft Project MSPDI format. * Presumes the input selection is a CPN file resource. * Output goes to MSPDI resource with {@code *.mspdi} extension. * @author mrb * @since 1.1 */ public class TranslatePlanningContent implements ICertWareConstants { /** model file encoding as XML resource */ private static final String FILE_ENCODING = "UTF-8"; /** default author tag for output */ private static final String AUTHOR_TAG = "CertWare Conversion from CPN"; /** default resource tag for output */ private static final String RESOURCE_NOTES_TAG = "CertWare Conversion from CPN"; /** default assignment tag for output */ private static final String ASSIGNMENT_NOTES_TAG = "CertWare Conversion from CPN"; /** default task notes tag for output */ private static final String TASK_NOTES_TAG = "Generated without calendars"; /** default task priority, based on OP example */ private static final int TASK_PRIORITY = 500; /** data type factory for XML durations */ DatatypeFactory datatypeFactory; /** task counter, reset with each construction */ private int taskCounter = 1; /** resource counter, reset with each construction */ private int resourceCounter = 1; /** assignment counter, reset with each construction */ private int assignmentCounter = 1; /** * Constructor creates the data type factory for XML fields. */ public TranslatePlanningContent() { taskCounter = 1; resourceCounter = 1; assignmentCounter = 1; try { datatypeFactory = DatatypeFactory.newInstance(); } catch (DatatypeConfigurationException e) { CertWareLog.logError("Configuring planning translator datatype factory", e); } } /** * Creates a project type record from the factory. * Populates it with default values from this action. * @param selectedFile selected CPN source file to translate * @return project type */ private ProjectType createProjectType(IFile selectedFile) { ProjectType projectType = MspdiFactory.eINSTANCE.createProjectType(); projectType.setActualsInSync(false); projectType.setAuthor(AUTHOR_TAG); projectType.setName(selectedFile.getName()); projectType.setProjectExternallyEdited(true); return projectType; } /** * Process the planning notation content. * @param selectedProject selected project, presumably an object contribution selection * @throws CoreException * @throws IOException */ public void processPlan(IFile selectedFile,IProgressMonitor monitor) throws CoreException, IOException { Plans selectedPlans = null; // check that input is provided if ( selectedFile == null ) { CertWareLog.logWarning("Selected file not provided to plan conversion action."); return; } // create an empty MSPDI model then populate it final DocumentRoot outputPlan = MspdiFactory.eINSTANCE.createDocumentRoot(); final ProjectType outputProject = createProjectType(selectedFile); outputPlan.setProject(outputProject); // load the selected model try { // load the XML file through the EMF resource set implementation ResourceSet resourceSet = new ResourceSetImpl(); Resource resource = resourceSet.getResource( URI.createPlatformResourceURI(selectedFile.getFullPath().toString(), true), true); selectedPlans = (Plans)resource.getContents().get(0); } catch( Exception exception ) { CertWareLog.logError(String.format("%s %s", "Document root null loading" + selectedFile.getName()), exception); return; } // iterate over the selected CPN model to populate the MSPDI model if ( selectedPlans.getPlans() == null || selectedPlans.getPlans().size() < 1 ) { CertWareLog.logWarning(String.format("%s %s","Selected CPN file contains no plans",selectedFile.getName())); return; } monitor.beginTask("Processing plan tasks and resources", selectedPlans.getPlans().size()); for ( Plan inputPlan : selectedPlans.getPlans() ) { processInputPlan(inputPlan,outputProject,monitor); monitor.worked(1); if ( monitor.isCanceled() ) { return; } } // write the resulting model to an MSP file // expecting preference to have no extension, so add it if necessary IPreferenceStore store = Activator.getDefault().getPreferenceStore(); String fileName = store.getString(PreferenceConstants.P_FILENAME_MSP); if ( fileName == null || fileName.isEmpty() ) { fileName = selectedFile.getName(); int lio = fileName.lastIndexOf('.'); if ( lio > 0 ) { fileName = fileName.substring(0,lio); } } if ( fileName.endsWith( ICertWareConstants.MSPDI_EXTENSION) == false ) { fileName = fileName + '.' + ICertWareConstants.MSPDI_EXTENSION; } // fully specify the path to the new file given the container IContainer selectedContainer = selectedFile.getParent(); final String modelFile = selectedContainer.getFullPath().toPortableString() + IPath.SEPARATOR + fileName; // create the resource in a workspace modify operation WorkspaceModifyOperation operation = new WorkspaceModifyOperation() { @Override protected void execute(IProgressMonitor progressMonitor) { try { // create a resource set and resource for a new file ResourceSet resourceSet = new ResourceSetImpl(); URI fileURI = URI.createPlatformResourceURI(modelFile, true); Resource resource = resourceSet.createResource(fileURI); resource.getContents().add(outputPlan); // save the contents of the resource to the file system Map<Object, Object> options = new HashMap<Object, Object>(); options.put(XMLResource.OPTION_ENCODING, FILE_ENCODING); resource.save(options); } catch (Exception e) { CertWareLog.logError(String.format("%s %s","Saving MSPDI file",modelFile),e); } } }; // modify the workspace try { operation.run(monitor); } catch (Exception e) { CertWareLog.logError(String.format("%s %s","Modifying workspace for", selectedFile.getName()),e); } monitor.done(); } /** * Process a plan. For each resource and task add it to the output model. * @param inputPlan from selected file * @param outputProject destination project model * @param monitor progress monitor */ private void processInputPlan(Plan inputPlan, ProjectType outputProject, IProgressMonitor monitor) { if (inputPlan == null || outputProject == null ) { return; } processTasks(inputPlan,outputProject,monitor); processResources(inputPlan,outputProject,monitor); processAssignments(inputPlan,outputProject,monitor); } /** * Process resource to task assignments. For each assignment add it to the output model. * Uses actual resource record for the assignment. * @param inputPlan input plan with both tasks and resources * @param outputProject output plan, updated to also contain assignments if necessary * @param monitor progress monitor */ private void processAssignments(Plan inputPlan, ProjectType outputProject, IProgressMonitor monitor) { if ( inputPlan == null || outputProject == null ) { return; } AssignmentsType assignments = outputProject.getAssignments(); if ( assignments == null ) { assignments = MspdiFactory.eINSTANCE.createAssignmentsType(); outputProject.setAssignments(assignments); } // assignments made with actuals records Allocation actual = inputPlan.getActual(); String facility = actual.getFacility(); String team = actual.getTeam(); String id = inputPlan.getId(); addAssignment(id,facility,outputProject); addAssignment(id,team,outputProject); } /** * Adds an assignment between task and resource. * Increments assignment counter if new assignment added to model. * @param taskName task name, used to find UID * @param resourceName resource name, used to find UID * @param outputProject project model */ private void addAssignment(String taskName, String resourceName,ProjectType outputProject) { ResourceType r = findResourceByName(resourceName,outputProject); TaskType t = findTaskByName(taskName,outputProject); if ( r != null && t != null ) { // if assignment already made, move on without creating new assignment for ( AssignmentType a : outputProject.getAssignments().getAssignment() ) { if ( a.getResourceUID().equals(r.getUID()) && a.getTaskUID().equals(t.getUID())) { return; } } // create new assignment AssignmentType assignment = MspdiFactory.eINSTANCE.createAssignmentType(); assignment.setUID(BigInteger.valueOf(assignmentCounter)); assignment.setResourceUID(r.getUID()); assignment.setTaskUID(t.getUID()); assignment.setNotes(ASSIGNMENT_NOTES_TAG); outputProject.getAssignments().getAssignment().add(assignment); assignmentCounter++; } } /** * Searches resource list for resource by name. * @param name resource name * @param outputProject project container with resources * @return resource object or null */ private ResourceType findResourceByName(String name,ProjectType outputProject) { if ( outputProject == null || name == null || outputProject.getResources() == null ) { return null; } for ( ResourceType r : outputProject.getResources().getResource() ) { if ( name.equalsIgnoreCase(r.getName())) { return r; } } return null; } /** * Searches tasks list for task by name. * @param name task name * @param outputProject project container with tasks * @return task object or null */ private TaskType findTaskByName(String name,ProjectType outputProject) { if ( outputProject == null || name == null || outputProject.getTasks() == null ) { return null; } for ( TaskType t : outputProject.getTasks().getTask() ) { if ( name.equalsIgnoreCase(t.getName())) { return t; } } return null; } /** * Process a plan task. * Uses the cost and duration fields as targets for the estimated cost and duration input. * Uses the actual cost and actual duration fields as targets for the actual cost and actual duration input. * @param inputPlan from selected file * @param outputProject destination project model * @param monitor progress monitor */ private void processTasks(Plan inputPlan, ProjectType outputProject, IProgressMonitor monitor) { // ensure the output has a tasks collector TasksType outputTasks = outputProject.getTasks(); if ( outputTasks == null ) { outputTasks = MspdiFactory.eINSTANCE.createTasksType(); outputProject.setTasks( outputTasks ); } // create a new task TaskType outputTask = MspdiFactory.eINSTANCE.createTaskType(); // estimate content Allocation estimated = inputPlan.getEstimated(); if ( estimated != null ) { Duration duration = datatypeFactory.newDurationDayTime(true, 0, estimated.getDuration(), 0, 0); outputTask.setEstimated(true); outputTask.setCost(BigDecimal.valueOf( estimated.getCost() )); outputTask.setDuration( duration ); } // actual content Allocation actual = inputPlan.getActual(); if ( actual != null ) { Duration duration = datatypeFactory.newDurationDayTime(true, 0, actual.getDuration(), 0, 0); Duration none = datatypeFactory.newDurationDayTime(true, 0, 0, 0, 0); outputTask.setActualCost( BigDecimal.valueOf(actual.getCost()) ); outputTask.setActualDuration( duration ); outputTask.setActualOvertimeCost(BigDecimal.valueOf(0)); outputTask.setActualOvertimeWork(none); } // other task attributes outputTask.setUID(BigInteger.valueOf(taskCounter)); outputTask.setID(BigInteger.valueOf(taskCounter)); outputTask.setName( inputPlan.getId() ); outputTask.setType(TypeType3._0); // based on OP export outputTask.setIsNull(false); outputTask.setOutlineLevel(BigInteger.valueOf(1)); outputTask.setPriority(BigInteger.valueOf(TASK_PRIORITY)); //outputTask.setStart(value); no calendars available //outputTask.setDuration(value); done in the actuals calc outputTask.setDurationFormat(DurationFormatType4._5); // based on OP export // outputTask.setWork(value); outputTask.setResumeValid(false); outputTask.setEffortDriven(false); outputTask.setRecurring(false); outputTask.setOverAllocated(false); // outputTask.setEstimated(true); done in the estimated calc outputTask.setMilestone(false); outputTask.setSummary(false); outputTask.setCritical(true); outputTask.setIsSubproject(false); outputTask.setIsSubprojectReadOnly(false); outputTask.setExternalTask(false); outputTask.setStartVariance(BigInteger.valueOf(5280000)); outputTask.setFinishVariance(BigInteger.valueOf(0)); outputTask.setWorkVariance(0.0f); outputTask.setFreeSlack(BigInteger.valueOf(0)); outputTask.setTotalSlack(BigInteger.valueOf(0)); outputTask.setFixedCost(0.0f); outputTask.setFixedCostAccrual(FixedCostAccrualType._3); // based on OP export outputTask.setPercentComplete(BigInteger.valueOf(0)); outputTask.setPercentWorkComplete(BigInteger.valueOf(0)); // outputTask.setCost(value); done in cost calcs outputTask.setOvertimeCost(BigDecimal.valueOf(0)); outputTask.setOvertimeWork(datatypeFactory.newDurationDayTime(true, 0, 0, 0, 0)); // outputTask.setActualStart(value); no calendars available // outputTask.setActualDuration(value); done in actual calc // outputTask.setActualCost(value); done in actual calc // outputTask.setActualOvertimeCost(BigDecimal.valueOf(0)); done in actual cost // outputTask.setActualWork(value); // outputTask.setActualOvertimeWork(value); done in actual calc outputTask.setRegularWork(datatypeFactory.newDuration(0)); outputTask.setRemainingDuration(datatypeFactory.newDuration(0)); outputTask.setRemainingCost(BigDecimal.valueOf(0)); outputTask.setRemainingWork(datatypeFactory.newDuration(0)); outputTask.setRemainingOvertimeCost(BigDecimal.valueOf(0)); outputTask.setRemainingWork(datatypeFactory.newDuration(0)); outputTask.setActualCost(BigDecimal.valueOf(0)); outputTask.setCV(0.0f); outputTask.setConstraintType(ConstraintTypeType._7); // based on OP outputTask.setCalendarUID(BigInteger.valueOf(-1)); // based on OP // outputTask.setConstraintDate(value); // no calendars outputTask.setLevelAssignments(false); outputTask.setLevelingCanSplit(false); outputTask.setIgnoreResourceCalendar(false); outputTask.setNotes(TASK_NOTES_TAG); outputTask.setHideBar(false); outputTask.setRollup(false); outputTask.setBCWS(0.0f); outputTask.setBCWP(0.0f); outputTask.setEarnedValueMethod(EarnedValueMethodType._0); // based on OP outputTask.setActualWorkProtected(datatypeFactory.newDuration(0)); outputTask.setActualOvertimeWorkProtected(datatypeFactory.newDuration(0)); // no baselines added taskCounter++; // add it to the list outputTasks.getTask().add(outputTask); } /** * Process a plan resource. * @param inputPlan from selected file * @param outputProject destination project model * @param monitor progress monitor */ private void processResources(Plan inputPlan, ProjectType outputProject, IProgressMonitor monitor) { // ensure the output has a resources collector ResourcesType outputResources = outputProject.getResources(); if ( outputResources == null ) { outputResources = MspdiFactory.eINSTANCE.createResourcesType(); outputProject.setResources( outputResources ); } // add actual and estimated resources to the resource list processResourceAllocation(inputPlan.getActual(),outputResources); processResourceAllocation(inputPlan.getEstimated(),outputResources); } /** * Creates a new resource using the factory, populates its fields. * If resource name is already in the resources list, or the name is empty, returns without doing anything. * Adds the new resource to the given resources list. * @param name resource name * @param resources resources list */ private void processNewResource(String name, ResourcesType resources) { if ( resourceExists(name,resources) == true ) { return; } ResourceType outputResource = MspdiFactory.eINSTANCE.createResourceType(); outputResource.setUID(BigInteger.valueOf(resourceCounter)); outputResource.setID(BigInteger.valueOf(resourceCounter)); outputResource.setName(name); // set resource type TypeType2 as well? outputResource.setMaxUnits(1.0f); outputResource.setPeakUnits(1.0f); outputResource.setCanLevel(false); outputResource.setType(TypeType2._0); // based on OP export outputResource.setIsNull(false); outputResource.setOverAllocated(false); outputResource.setStandardRateFormat(StandardRateFormatType1._3); // based on OP export outputResource.setOvertimeRateFormat(OvertimeRateFormatType1._3); // based on OP export outputResource.setWork(datatypeFactory.newDuration(0)); outputResource.setWorkVariance(0.0f); outputResource.setPercentWorkComplete(BigInteger.valueOf(0)); outputResource.setOvertimeCost(BigDecimal.valueOf(0)); outputResource.setOvertimeWork(datatypeFactory.newDurationDayTime(true, 0, 0, 0, 0)); outputResource.setRegularWork(datatypeFactory.newDuration(0)); outputResource.setRemainingCost(BigDecimal.valueOf(0)); outputResource.setRemainingWork(datatypeFactory.newDuration(0)); outputResource.setRemainingOvertimeCost(BigDecimal.valueOf(0)); outputResource.setRemainingWork(datatypeFactory.newDuration(0)); outputResource.setActualCost(BigDecimal.valueOf(0)); outputResource.setCost(BigDecimal.valueOf(0)); outputResource.setCostPerUse(BigDecimal.valueOf(0)); outputResource.setACWP(0.0f); outputResource.setSV(0.0f); outputResource.setCV(0.0f); outputResource.setBCWS(0.0f); outputResource.setBCWP(0.0f); outputResource.setCalendarUID(BigInteger.valueOf(2)); // based on OP outputResource.setNotes(RESOURCE_NOTES_TAG); outputResource.setActualWorkProtected(datatypeFactory.newDuration(0)); outputResource.setActualOvertimeWorkProtected(datatypeFactory.newDuration(0)); outputResource.setIsGeneric(false); outputResource.setIsInactive(false); // no baselines added resources.getResource().add(outputResource); resourceCounter++; } /** * Given an allocation, create resource records for the team and facility. * @param a allocation * @param resources resource list, adding new resources to the list if allocation fields missing */ private void processResourceAllocation(Allocation a, ResourcesType resources) { if ( a != null ) { processNewResource(a.getFacility(),resources); processNewResource(a.getTeam(),resources); } } /** * Determines whether resource name is already in the resources list, matching name field. * @param name resource name, returns true if name is null or empty * @param resources resource list * @return true if name string matches, ignoring case */ private boolean resourceExists(String name, ResourcesType resources) { if ( name == null || name.isEmpty() ) return true; for ( ResourceType r : resources.getResource()) { if ( name.equalsIgnoreCase(r.getName())) { return true; } } return false; } }