/*==========================================================================*\ | $Id: Assignment.java,v 1.6 2012/05/09 16:19:41 stedwar2 Exp $ |*-------------------------------------------------------------------------*| | Copyright (C) 2006-2012 Virginia Tech | | This file is part of Web-CAT. | | Web-CAT is free software; you can redistribute it and/or modify | it under the terms of the GNU Affero General Public License as published | by the Free Software Foundation; either version 3 of the License, or | (at your option) any later version. | | Web-CAT is distributed in the hope that it will be useful, | but WITHOUT ANY WARRANTY; without even the implied warranty of | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | GNU General Public License for more details. | | You should have received a copy of the GNU Affero General Public License | along with Web-CAT; if not, see <http://www.gnu.org/licenses/>. \*==========================================================================*/ package org.webcat.grader; import com.webobjects.foundation.*; import com.webobjects.eocontrol.*; import er.extensions.eof.ERXKey; import er.extensions.foundation.ERXArrayUtilities; import java.io.File; import java.util.*; import org.webcat.core.MutableDictionary; import org.webcat.core.User; import org.apache.log4j.Logger; import org.webcat.core.*; // ------------------------------------------------------------------------- /** * An assignment that can be given in one or more classes. * * @author Stephen Edwards * @author Last changed by $Author: stedwar2 $ * @version $Revision: 1.6 $, $Date: 2012/05/09 16:19:41 $ */ public class Assignment extends _Assignment { //~ Constructors .......................................................... // ---------------------------------------------------------- /** * Creates a new Assignment object. */ public Assignment() { super(); } //~ Constants (for key names) ............................................. public static final String COURSE_OFFERINGS_KEY = OFFERINGS_KEY + "." + AssignmentOffering.COURSE_OFFERING_KEY; public static final ERXKey<CourseOffering> courseOfferings = new ERXKey<CourseOffering>(COURSE_OFFERINGS_KEY); public static final String COURSES_KEY = COURSE_OFFERINGS_KEY + "." + CourseOffering.COURSE_KEY; public static final ERXKey<Course> courses = new ERXKey<Course>(COURSES_KEY); public static final String SEMESTERS_KEY = COURSE_OFFERINGS_KEY + "." + CourseOffering.SEMESTER_KEY; public static final ERXKey<Semester> semesters = new ERXKey<Semester>(SEMESTERS_KEY); public static final String ID_FORM_KEY = "aid"; //~ Public Methods ........................................................ // ---------------------------------------------------------- public String subdirName() { if ( cachedSubdirName == null ) { String myName = name(); cachedSubdirName = AuthenticationDomain.subdirNameOf( myName ); log.debug( "trimmed name '" + myName + "' to '" + cachedSubdirName + "'" ); } return cachedSubdirName; } // ---------------------------------------------------------- /** * Get a short (no longer than 60 characters) description of this * assignment. * @return the description */ public String userPresentableDescription() { String result = name(); if ( offerings().count() > 0 ) { try { result += " (" + offerings().objectAtIndex( 0 ) .courseOffering().course().deptNumber() + ")"; } catch (Exception e) { // In case there is an NPE because this object is in the // process of being deleted result += "(unknown)"; } } return result; } // ---------------------------------------------------------- public void setName( String value ) { if ( dirNeedingRenaming == null && name() != null ) { dirNeedingRenaming = subdirName(); } cachedSubdirName = null; if (value != null) { value = value.trim(); } super.setName(value); } // ---------------------------------------------------------- public Object validateName( Object value ) { if ( value == null ) { log.debug( "conflict exists, throwing exception" ); throw new ValidationException( "Please provide an assignment name." ); } log.debug("my snapshot" + snapshot()); String result = value.toString().trim(); String newSubdirName = AuthenticationDomain.subdirNameOf( result ); log.debug( "validateName(" + result + ")" ); log.debug( "subdir = " + newSubdirName ); if ( !newSubdirName.equals( subdirName() ) && conflictingSubdirNameExists( newSubdirName ) ) { log.debug( "conflict exists, throwing exception" ); throw new ValidationException( "The name '" + result + "' conflicts with an existing " + "assignment. Please choose another name." ); } log.debug( "no conflict found" ); return result; } // ---------------------------------------------------------- /* (non-Javadoc) * @see er.extensions.eof.ERXGenericRecord#didUpdate() */ public void didUpdate() { super.didUpdate(); if ( dirNeedingRenaming != null ) { renameSubdirs( dirNeedingRenaming, subdirName() ); dirNeedingRenaming = null; } } // ---------------------------------------------------------- public String titleString() { String result = name(); if ( shortDescription() != null ) { result += ": " + shortDescription(); } return result; } // ---------------------------------------------------------- public Step addNewStep( GradingPlugin script ) { int position = steps().count() + 1; Step step = createStepsRelationship(); step.setOrder( position ); step.setGradingPluginRelationship( script ); return step; } // ---------------------------------------------------------- public Step copyStep( Step step, boolean keepOrdering ) { Step newStep = addNewStep( step.gradingPlugin() ); newStep.setTimeout( step.timeout() ); newStep.setConfigRelationship( step.config() ); MutableDictionary dict = step.configSettings(); if ( dict != null ) { newStep.setConfigSettings( new MutableDictionary( dict ) ); } if ( keepOrdering ) { newStep.setOrder( step.order() ); } return newStep; } // ---------------------------------------------------------- public boolean usesTestingScore() { SubmissionProfile profile = submissionProfile(); return profile != null && ( profile.correctnessPoints() > 0.0 ); } // ---------------------------------------------------------- public boolean usesToolCheckScore() { SubmissionProfile profile = submissionProfile(); return profile != null && ( profile.toolPointsRaw() != null && profile.toolPoints() != 0 ); } // ---------------------------------------------------------- public boolean usesTAScore() { SubmissionProfile profile = submissionProfile(); return profile != null && ( profile.taPoints() > 0.0 ); } // ---------------------------------------------------------- public boolean usesBonusesOrPenalties() { SubmissionProfile profile = submissionProfile(); return profile != null && ( profile.awardEarlyBonus() || profile.deductLatePenalty() ); } // ---------------------------------------------------------- /** * {@inheritDoc} */ @Override public boolean accessibleByUser(User user) { for (AssignmentOffering offering : offerings()) { if (offering.accessibleByUser(user)) { return true; } } return false; } // ---------------------------------------------------------- public NSTimestamp commonOfferingsDueDate() { NSTimestamp common = null; NSArray<AssignmentOffering> myOfferings = offerings(); if ( myOfferings.count() > 1 ) { for (AssignmentOffering ao : myOfferings) { if ( common == null ) { common = ao.dueDate(); } else if ( common.compare( ao.dueDate() ) != NSComparator.OrderedSame ) { common = null; break; } } } return common; } // ---------------------------------------------------------- public AssignmentOffering offeringForUser( User user ) { AssignmentOffering offering = null; NSDictionary<String, Object> userBinding = new NSDictionary<String, Object>( user, "user" ); // First, check to see if the user is a student in any of the // course offerings associated with the available assignment offerings NSArray<?> results = ERXArrayUtilities .filteredArrayWithEntityFetchSpecification( offerings(), AssignmentOffering.ENTITY_NAME, AssignmentOffering.OFFERINGS_WITH_USER_AS_STUDENT_FSPEC, userBinding ); if ( results == null || results.count() == 0 ) { // if the user is not found as a student, check for staff instead results = ERXArrayUtilities .filteredArrayWithEntityFetchSpecification( offerings(), AssignmentOffering.ENTITY_NAME, AssignmentOffering.OFFERINGS_WITH_USER_AS_STAFF_FSPEC, userBinding ); } if ( results != null && results.count() > 0 ) { offering = (AssignmentOffering)results.objectAtIndex( 0 ); } return offering; } // ---------------------------------------------------------- /** * Retrieve all the other assignments that share a course offering with * one of this assignment's offerings. * * @param context The editing context to use * @return an NSArray of the entities retrieved */ public NSArray<Assignment> objectsForNeighborAssignments( EOEditingContext context ) { NSMutableArray<Assignment> results = new NSMutableArray<Assignment>(); for (AssignmentOffering offering : offerings()) { ERXArrayUtilities.addObjectsFromArrayWithoutDuplicates(results, neighborAssignments( context, offering.courseOffering())); } results.remove(this); return results; } // ---------------------------------------------------------- public static boolean namesAreSimilar( String name1, String name2 ) { boolean result = false; int limit = Math.min( name1.length(), name2.length() ); for ( int i = 0; i < limit; i++ ) { if ( Character.isLetter( name1.charAt( i ) ) ) { result = ( name1.charAt( i ) == name2.charAt( i ) ); if ( !result ) break; } else { break; } } return result; } // ---------------------------------------------------------- /** * This EOQualifier matches any assignment with a directory name * that does not match (case-insensitive) any existing assignment offering * associated with a given course offering--in other words, succeeds * for assignments that would have unique names. */ public static class NonDuplicateAssignmentNameQualifier extends EOQualifier { // ---------------------------------------------------------- /** * Create a new qualifier for assignments in the given course. * @param courseOffering the course offering to check against */ public NonDuplicateAssignmentNameQualifier( CourseOffering courseOffering) { if (courseOffering != null) { lcNames = new HashSet<String>(); NSArray<AssignmentOffering> assignments = AssignmentOffering.offeringsForCourseOffering( courseOffering.editingContext(), courseOffering ); for (AssignmentOffering ao : assignments) { String newName = ao.assignment().subdirName(); if (newName != null) { lcNames.add( newName.toLowerCase() ); } } } } // ---------------------------------------------------------- /** * Create a new qualifier for assignments in the given course. * @param lcNames the set of existing assignment names (assumed to all * be lowercase-only) to check against */ public NonDuplicateAssignmentNameQualifier(Set<String> lcNames) { this.lcNames = lcNames; } // ---------------------------------------------------------- @Override @SuppressWarnings("unchecked") public void addQualifierKeysToSet( NSMutableSet qualifierKeys ) { qualifierKeys.add("subdirName"); } // ---------------------------------------------------------- @Override @SuppressWarnings("unchecked") public EOQualifier qualifierWithBindings( NSDictionary bindings, boolean requiresAll ) { Object courseOfferingBinding = bindings.valueForKey( "courseOffering"); if (courseOfferingBinding == null) { return new NonDuplicateAssignmentNameQualifier((Set)null); } else { return new NonDuplicateAssignmentNameQualifier( (CourseOffering)courseOfferingBinding); } } // ---------------------------------------------------------- @Override public void validateKeysWithRootClassDescription( EOClassDescription classDescription ) { if (!classDescription.entityName().equals(ENTITY_NAME)) { throw new RuntimeException("This qualifier can only be " + "applied to " + ENTITY_NAME + " objects."); } } // ---------------------------------------------------------- @Override public boolean evaluateWithObject( Object object ) { String subdirName = ((Assignment)object).subdirName(); return subdirName != null && ( lcNames == null || !lcNames.contains( subdirName.toLowerCase() ) ); } //~ Instance/static variables ......................................... private Set<String> lcNames; } //~ Private Methods ....................................................... // ---------------------------------------------------------- /* package */ boolean conflictingSubdirNameExists( String subdir ) { NSArray<Assignment> neighbors = objectsForNeighborAssignments(editingContext()); if (log.isDebugEnabled()) { log.debug( "neighbors = " + neighbors ); } if (subdir != null) { subdir = subdir.toLowerCase(); } for (Assignment asgn : neighbors) { String yourSub = asgn.subdirName(); if (yourSub != null) { yourSub = yourSub.toLowerCase(); } if ( (subdir == null && yourSub == null) || (subdir != null && subdir.equals(yourSub))) { return true; } } return false; } // ---------------------------------------------------------- private void renameSubdirs( String oldSubdir, String newSubdir ) { NSArray<AuthenticationDomain> domains = AuthenticationDomain.authDomains(); for (AuthenticationDomain domain : domains) { NSArray<AssignmentOffering> myOfferings = offerings(); StringBuffer dir = domain.submissionBaseDirBuffer(); int baseDirLen = dir.length(); String msgs = null; for (AssignmentOffering offering : myOfferings) { // clear out old suffix dir.delete( baseDirLen, dir.length() ); offering.courseOffering().addSubdirTo( dir ); dir.append('/'); dir.append( oldSubdir ); File oldDir = new File( dir.toString() ); if ( oldDir.exists() ) { dir.delete( baseDirLen, dir.length() ); offering.courseOffering().addSubdirTo( dir ); dir.append('/'); dir.append( newSubdir ); File newDir = new File( dir.toString() ); if (!oldDir.renameTo( newDir )) { msgs = (msgs == null ? "" : (msgs + " ")) + "Failed to rename directory: " + oldDir + " => " + newDir; } } } if (msgs != null) { throw new RuntimeException(msgs); } } } //~ Instance/static variables ............................................. private String cachedSubdirName = null; private String dirNeedingRenaming = null; static Logger log = Logger.getLogger( Assignment.class ); }