text:
package org.unitime.timetable.gwt.shared;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.unitime.timetable.gwt.resources.StudentSectioningMessages;
import org.unitime.timetable.gwt.shared.ClassAssignmentInterface.CourseAssignment;
import org.unitime.timetable.gwt.shared.CourseRequestInterface.RequestedCourse;

import com.google.gwt.user.client.rpc.IsSerializable;

/**
 * @author Tomas Muller
 */
public class DegreePlanInterface implements IsSerializable, Serializable {
	private static final long serialVersionUID = 1L;
	private Long iStudentId, iSessionId;

	private String iId, iName, iDegree, iSchool, iTrack, iModifiedWho;
	private Date iModified;
	private DegreeGroupInterface iGroup;

	private boolean iLocked = false, iActive = false;

	public DegreePlanInterface() {
	}

	public Long getStudentId() { return iStudentId; }
	public void setStudentId(Long studentId) { iStudentId = studentId; }
	public Long getSessionId() { return iSessionId; }
	public void setSessionId(Long sessionId) { iSessionId = sessionId; }
	public String getId() { return iId; }
	public void setId(String id) { iId = id; }
	public String getName() { return iName; }
	public void setName(String name) { iName = name; }
	public String getDegree() { return iDegree; }
	public void setDegree(String degree) { iDegree = degree; }
	public String getSchool() { return iSchool; }
	public void setSchool(String school) { iSchool = school; }
	public String getTrackingStatus() { return iTrack; }
	public void setTrackingStatus(String track) { iTrack = track; }
	public Date getLastModified() { return iModified; }
	public void setLastModified(Date modified) { iModified = modified; }
	public String getModifiedWho() { return iModifiedWho; }
	public void setModifiedWho(String name) { iModifiedWho = name; }
	public DegreeGroupInterface getGroup() { return iGroup; }
	public void setGroup(DegreeGroupInterface group) { iGroup = group; }
	public boolean isActive() { return iActive; }
	public void setActive(boolean active) { iActive = active; }
	public boolean isLocked() { return iLocked; }
	public void setLocked(boolean locked) { iLocked = locked; }

	public List<DegreeCourseInterface> listSelected(boolean pickFirstWhenNoneSelected) {
		List<DegreeCourseInterface> ret = new ArrayList<DegreeCourseInterface>();
		if (iGroup != null) iGroup.listSelectedCriticalFirst(ret, pickFirstWhenNoneSelected);
		return ret;
	}

	public List<CourseAssignment> listAlternatives(DegreeCourseInterface course) {
		List<CourseAssignment> ret = new ArrayList<CourseAssignment>();
		if (iGroup != null) iGroup.listAlternatives(ret, course);
		if (course.hasMultiSelection())
			for (String id: course.getMultiSelection()) {
				CourseAssignment ca = course.getCourse(id);
				if (ca != null && !ca.getCourseId().equals(course.getCourseId())) ret.add(ca);
			}
		return ret;
	}

	public boolean hasCourse(String course) {
		if (iGroup != null) return iGroup.hasCourse(course);
		return false;
	}

	public boolean isCourseSelected(String course) {
		if (iGroup != null) return iGroup.isCourseSelected(course);
		return false;
	}

	public boolean hasCourse(RequestedCourse course) {
		if (iGroup != null) return iGroup.hasCourse(course);
		return false;
	}

	public boolean isCourseSelected(RequestedCourse course) {
		if (iGroup != null) return iGroup.isCourseSelected(course);
		return false;
	}

	public boolean isCourseCritical(RequestedCourse course) {
		if (iGroup != null) return iGroup.isCourseCritical(course);
		return false;
	}

	public String getPlaceHolder(DegreeCourseInterface course) {
		if (iGroup != null) return iGroup.getPlaceHolder(course);
		return null;
	}

	@Override
	public String toString() { return iName + ": " + iGroup; }

	public static abstract class DegreeItemInterface implements IsSerializable, Serializable {
		private static final long serialVersionUID = 1L;
		private String iId = null;
		List<String> iSelection = null;

		public String getId() { return iId; }
		public void setId(String id) { iId = id; }

		public int getMultiSelection(String id) {
			if (iSelection == null) return -1;
			return iSelection.indexOf(id);
		}

		public void setMultiSelection(String id, boolean selected) {
			if (iSelection == null) iSelection = new ArrayList<String>();
			if (selected && !iSelection.contains(id))
				iSelection.add(id);
			if (!selected)
				iSelection.remove(id);
		}

		public boolean hasMultiSelection() {
			return iSelection != null && !iSelection.isEmpty();
		}
	}

	public static abstract class DegreeMultiSelectionInterface extends DegreeItemInterface {
		private static final long serialVersionUID = 1L;
		List<String> iSelection = null;

		public int getMultiSelection(String id) {
			if (iSelection == null) return -1;
			return iSelection.indexOf(id);
		}

		public void setMultiSelection(String id, boolean selected) {
			if (iSelection == null) iSelection = new ArrayList<String>();
			if (selected) {
				if (!iSelection.contains(id))
					iSelection.add(id);
			} else {
				iSelection.remove(id);
			}
		}

		public boolean hasMultiSelection() {
			return iSelection != null && !iSelection.isEmpty();
		}
		public List<String> getMultiSelection() {
			return iSelection;
		}
	}

	public static class DegreeGroupInterface extends DegreeMultiSelectionInterface {
		private static final long serialVersionUID = 1L;
		private boolean iChoice = false, iPlaceHolder = false;;
		List<DegreeCourseInterface> iCourses = null;
		List<DegreeGroupInterface> iGroups = null;
		List<DegreePlaceHolderInterface> iPlaceHolders = null;
		private Boolean iSelected = null;
		private String iDescription = null;
		private Boolean iCritical = null;

		public DegreeGroupInterface() {}

		public boolean isChoice() { return iChoice; }
		public void setChoice(boolean choice) { iChoice = choice; }
		public boolean isPlaceHolder() { return iPlaceHolder; }
		public void setPlaceHolder(boolean placeHolder) { iPlaceHolder = placeHolder; }
		public boolean hasCourses() { return iCourses != null && !iCourses.isEmpty(); }
		public boolean hasMultipleCourses() {
			if (iCourses == null) return false;
			for (DegreeCourseInterface course: iCourses)
				if (course.hasMultipleCourses()) return true;
			return false;
		}
		public List<DegreeCourseInterface> getCourses() { return iCourses; }
		public void addCourse(DegreeCourseInterface course) {
			if (iCourses == null) iCourses = new ArrayList<DegreeCourseInterface>();
			iCourses.add(course);
		}
		public DegreeCourseInterface pickCourse() {
			if (iCourses == null) return null;
			AvailabilityComparator cmp = new AvailabilityComparator();
			DegreeCourseInterface ret = null;
			for (DegreeCourseInterface course: iCourses)
				if (ret == null || cmp.compare(course, ret) < 0)
					ret = course;
			return ret;
		}
		public boolean hasGroups() { return iGroups != null && !iGroups.isEmpty(); }
		public List<DegreeGroupInterface> getGroups() { return iGroups; }
		public void addGroup(DegreeGroupInterface group) {
			if (iGroups == null) iGroups = new ArrayList<DegreeGroupInterface>();
			iGroups.add(group);
		}
		public void merge(DegreeGroupInterface group) {
			if (group.hasCourses())
				for (DegreeCourseInterface course: group.getCourses())
					addCourse(course);
			if (group.hasPlaceHolders())
				for (DegreePlaceHolderInterface ph: group.getPlaceHolders())
					addPlaceHolder(ph);
			if (group.hasGroups())
				for (DegreeGroupInterface g: group.getGroups()) {
					if (isChoice() == g.isChoice())
						merge(g);
					else
						addGroup(g);
				}
		}
		public boolean hasPlaceHolders() { return iPlaceHolders != null && !iPlaceHolders.isEmpty(); }
		public List<DegreePlaceHolderInterface> getPlaceHolders() { return iPlaceHolders; }
		public void addPlaceHolder(DegreePlaceHolderInterface placeHolder) {
			if (iPlaceHolders == null) iPlaceHolders = new ArrayList<DegreePlaceHolderInterface>();
			iPlaceHolders.add(placeHolder);
		}
		public boolean hasSelected() { return iSelected != null; }
		public boolean isSelected() { return iSelected == null || iSelected.booleanValue(); }
		public void setSelected(boolean selected) { iSelected = selected; }

		public boolean hasCritical() { return iCritical != null; }
		public boolean isCritical() { return iCritical != null && iCritical.booleanValue(); }
		public void setCritical(boolean critical) { iCritical = critical; }

		public boolean hasDescription() { return iDescription != null && !iDescription.isEmpty(); }
		public String getDescription() { return hasDescription() ? iDescription : toString(); }
		public void setDescription(String description) { iDescription = description; }

		public int countItems() {
			return (hasPlaceHolders() ? getPlaceHolders().size() : 0) + (hasGroups() ? getGroups().size() : 0) + (hasCourses() ? getCourses().size() : 0);
		}

		public boolean isUnionGroupWithOneChoice() {
			if (isChoice()) return false;
			int nrChoices = (hasPlaceHolders() ? getPlaceHolders().size() : 0) +
					(hasGroups() ? getGroups().size() : 0) +
					(hasCourses() ? getCourses().size() : 0);
			return nrChoices == 1;
		}

		public int getMaxDepth() {
			if (iGroups == null || iGroups.isEmpty()) return (!isChoice() && hasMultipleCourses() ? 2 : 1);
			int ret = 0;
			for (DegreeGroupInterface g: iGroups)
				if (ret < g.getMaxDepth())
					ret = g.getMaxDepth();
			return 1 + ret;
		}

		@Override
		public String toString() {
			if (isPlaceHolder() && hasDescription()) return getDescription();
			String ret = "";
			if (iCourses != null)
				for (DegreeCourseInterface course: iCourses)
					ret += (ret.isEmpty() ? "" : iChoice ? " or " : " and ") + course;
			if (iGroups != null)
				for (DegreeGroupInterface group: iGroups)
					ret += (ret.isEmpty() ? "" : iChoice ? " or " : " and ") + "(" + group + ")";
			if (iPlaceHolders != null)
				for (DegreePlaceHolderInterface ph: iPlaceHolders)
					ret += (ret.isEmpty() ? "" : iChoice ? " or " : " and ") + ph;
			return ret;
		}

		public String toString(StudentSectioningMessages MESSAGES) {
			if (isPlaceHolder() && hasDescription()) return getDescription();
			List<String> items = new ArrayList<String>();
			if (iCourses != null)
				for (DegreeCourseInterface course: iCourses)
					items.add(MESSAGES.course(course.getSubject(), course.getCourse()));
			if (iGroups != null)
				for (DegreeGroupInterface group: iGroups)
					items.add(MESSAGES.surroundWithBrackets(group.toString(MESSAGES)));
			if (iPlaceHolders != null)
				for (DegreePlaceHolderInterface ph: iPlaceHolders)
					items.add(ph.getType());
			switch (items.size()) {
			case 0:
				return "";
			case 1:
				return items.get(0);
			case 2:
				return (isChoice() ? MESSAGES.choiceSeparatorPair(items.get(0), items.get(1)) : MESSAGES.courseSeparatorPair(items.get(0), items.get(1)));
			default:
				String ret = null;
				for (Iterator<String> i = items.iterator(); i.hasNext(); ) {
					String item = i.next();
					if (ret == null)
						ret = item;
					else {
						if (i.hasNext()) {
							ret = (isChoice() ? MESSAGES.choiceSeparatorMiddle(ret, item) : MESSAGES.courseSeparatorMiddle(ret, item));
						} else {
							ret = (isChoice() ? MESSAGES.choiceSeparatorLast(ret, item) : MESSAGES.courseSeparatorLast(ret, item));
						}
					}
				}
				return ret;
			}
		}

		protected void listSelectedCriticalFirst(List<DegreeCourseInterface> requested, boolean pickFirstWhenNoneSelected) {
			boolean hasSelection = false;
			if (hasCourses())
				for (DegreeCourseInterface course: getCourses()) {
					if (!course.isCritical()) continue;
					if (!isChoice() || course.isSelected()) {
						requested.add(course); hasSelection = true;
					}
				}
			if (hasGroups())
				for (DegreeGroupInterface g: getGroups()) {
					if (!g.isCritical()) continue;
					if (!isChoice() || g.isSelected()) {
						g.listSelected(requested, pickFirstWhenNoneSelected); hasSelection = true;
					}
				}
			if (hasCourses())
				for (DegreeCourseInterface course: getCourses()) {
					if (course.isCritical()) continue;
					if (!isChoice() || course.isSelected()) {
						requested.add(course); hasSelection = true;
					}
				}
			if (hasGroups())
				for (DegreeGroupInterface g: getGroups()) {
					if (g.isCritical()) continue;
					if (!isChoice() || g.isSelected()) {
						g.listSelected(requested, pickFirstWhenNoneSelected); hasSelection = true;
					}
				}
			if (isChoice() && !hasSelection && pickFirstWhenNoneSelected) {
				if (isPlaceHolder() && hasDescription() && hasCourses() && getCourses().size() > 10) {
					DegreeCourseInterface c = new DegreeCourseInterface();
					c.setCourse(getDescription());
					requested.add(c);
				} else {
					if (hasCourses())
						requested.add(pickCourse());
					else if (hasGroups())
						getGroups().get(0).listSelected(requested, pickFirstWhenNoneSelected);
				}
			}
			if (hasPlaceHolders() && pickFirstWhenNoneSelected) {
				for (DegreePlaceHolderInterface h: getPlaceHolders()) {
					DegreeCourseInterface c = new DegreeCourseInterface();
					c.setCourse(h.getName());
					requested.add(c);
				}
			}
		}

		protected void listSelected(List<DegreeCourseInterface> requested, boolean pickFirstWhenNoneSelected) {
			boolean hasSelection = false;
			if (hasCourses())
				for (DegreeCourseInterface course: getCourses())
					if (!isChoice() || course.isSelected()) {
						requested.add(course); hasSelection = true;
					}
			if (hasGroups())
				for (DegreeGroupInterface g: getGroups())
					if (!isChoice() || g.isSelected()) {
						g.listSelected(requested, pickFirstWhenNoneSelected); hasSelection = true;
					}
			if (isChoice() && !hasSelection && pickFirstWhenNoneSelected) {
				if (isPlaceHolder() && hasDescription() && hasCourses()) {
					DegreeCourseInterface c = new DegreeCourseInterface();
					c.setCourse(getDescription());
					requested.add(c);
				} else {
					if (hasCourses())
						requested.add(pickCourse());
					else if (hasGroups())
						getGroups().get(0).listSelected(requested, pickFirstWhenNoneSelected);
				}
			}
			if (hasPlaceHolders() && pickFirstWhenNoneSelected) {
				for (DegreePlaceHolderInterface h: getPlaceHolders()) {
					DegreeCourseInterface c = new DegreeCourseInterface();
					c.setCourse(h.getName());
					requested.add(c);
				}
			}
		}

		public boolean hasCourse(DegreeCourseInterface course) {
			if (hasCourses())
				for (DegreeCourseInterface c: getCourses())
					if (c.getId().equals(course.getId())) return true;
			if (hasGroups())
				for (DegreeGroupInterface g: getGroups())
					if (g.hasCourse(course)) return true;
			return false;
		}

		public DegreeCourseInterface getCourse(Long courseId) {
			if (hasCourses())
				for (DegreeCourseInterface c: getCourses())
					if (c.hasCourse(courseId)) return c;
			if (hasGroups())
				for (DegreeGroupInterface g: getGroups()) {
					DegreeCourseInterface c = g.getCourse(courseId);
					if (c != null) return c;
				}
			return null;
		}

		public DegreeCourseInterface getCourse(String courseId) {
			if (hasCourses())
				for (DegreeCourseInterface c: getCourses())
					if (c.hasCourse(courseId)) return c;
			if (hasGroups())
				for (DegreeGroupInterface g: getGroups()) {
					DegreeCourseInterface c = g.getCourse(courseId);
					if (c != null) return c;
				}
			return null;
		}

		public DegreeGroupInterface getGroup(String groupId) {
			if (hasGroups())
				for (DegreeGroupInterface g: getGroups())
					if (g.getId().equals(groupId)) return g;
			return null;
		}

		public String getPlaceHolder(DegreeCourseInterface course) {
			if (isPlaceHolder() && hasDescription() && hasCourses())
				for (DegreeCourseInterface c: getCourses())
					if (c.getId().equals(course.getId())) return getDescription();
			if (hasGroups())
				for (DegreeGroupInterface g: getGroups()) {
					String ph = g.getPlaceHolder(course);
					if (ph != null) return ph;
				}
			return null;
		}

		protected void listAlternatives(List<CourseAssignment> alternatives, DegreeCourseInterface course) {
			if (isChoice()) {
				if (hasCourse(course) && hasMultiSelection()) {
					for (String id: getMultiSelection()) {
						DegreeCourseInterface selected = getCourse(id);
						if (selected != null) {
							CourseAssignment ca = selected.getCourse(id);
							if (ca != null && !ca.getCourseId().equals(course.getCourseId())) alternatives.add(ca);
						}
						DegreeGroupInterface group = getGroup(id);
						if (group != null && !group.isChoice() && group.hasCourses() && !group.hasCourse(course)) {
							for (DegreeCourseInterface c: group.getCourses()) {
								CourseAssignment ca = c.getSelectedCourse(true);
								if (ca != null) alternatives.add(ca);
							}
						}
					}
				}
			} else {
				if (hasGroups())
					for (DegreeGroupInterface g: getGroups())
						g.listAlternatives(alternatives, course);
			}
		}

		public boolean hasSelection() {
			if (hasCourses())
				for (DegreeCourseInterface course: getCourses())
					if (!isChoice() || course.isSelected()) return true;
			if (hasGroups())
				for (DegreeGroupInterface g: getGroups())
					if (!isChoice() || g.isSelected()) return true;
			return false;
		}

		protected boolean hasCourse(String name) {
			if (iCourses != null)
				for (DegreeCourseInterface course: iCourses) {
					if (course.hasCourses()) {
						for (CourseAssignment ca: course.getCourses())
							if (name.equalsIgnoreCase(ca.getCourseName()) || name.equalsIgnoreCase(ca.getCourseNameWithTitle())) return true;
					}
					if (name.equals(course.getCourseName()) || name.equalsIgnoreCase(course.getCourseNameWithTitle())) return true;
				}
			if (iGroups != null)
				for (DegreeGroupInterface g: iGroups)
					if (g.hasCourse(name)) return true;
			return false;
		}

		protected boolean isCourseSelected(String name) {
			if (iCourses != null)
				for (DegreeCourseInterface course: iCourses) {
					if (isChoice() && !course.isSelected()) continue;
					if (course.hasCourses() && course.getCourseId() != null) {
						for (CourseAssignment ca: course.getCourses())
							if (course.getCourseId().equals(ca.getCourseId()) && (name.equalsIgnoreCase(ca.getCourseName()) || name.equalsIgnoreCase(ca.getCourseNameWithTitle()))) return true;
					} else {
						if (name.equals(course.getCourseName()) || name.equalsIgnoreCase(course.getCourseNameWithTitle())) return true;
					}
				}
			if (iGroups != null)
				for (DegreeGroupInterface g: iGroups) {
					if ((!isChoice() || g.isSelected()) && g.hasCourse(name)) return true;
				}
			return false;
		}

		protected boolean hasCourse(RequestedCourse rc) {
			if (iCourses != null)
				for (DegreeCourseInterface course: iCourses) {
					if (course.hasCourses()) {
						for (CourseAssignment ca: course.getCourses())
							if (rc.equals(ca)) return true;
					}
					if (rc.equals(course)) return true;
				}
			if (iGroups != null)
				for (DegreeGroupInterface g: iGroups)
					if (g.hasCourse(rc)) return true;
			return false;
		}

		protected boolean isCourseSelected(RequestedCourse rc) {
			if (iCourses != null)
				for (DegreeCourseInterface course: iCourses) {
					if (isChoice() && !course.isSelected()) continue;
					if (course.hasCourses() && course.getCourseId() != null) {
						for (CourseAssignment ca: course.getCourses())
							if (course.getCourseId().equals(ca.getCourseId()) && rc.equals(ca)) return true;
					} else {
						if (rc.equals(course)) return true;
					}
				}
			if (iGroups != null)
				for (DegreeGroupInterface g: iGroups) {
					if ((!isChoice() || g.isSelected()) && g.hasCourse(rc)) return g.isCourseSelected(rc);
				}
			return false;
		}

		protected boolean isCourseCritical(RequestedCourse rc) {
			if (iCourses != null)
				for (DegreeCourseInterface course: iCourses) {
					if (course.hasCourses() && course.getCourseId() != null) {
						for (CourseAssignment ca: course.getCourses())
							if (course.getCourseId().equals(ca.getCourseId()) && rc.equals(ca))
								return isCritical() || course.isCritical();
					} else {
						if (rc.equals(course)) return isCritical() || course.isCritical();
					}
				}
			if (iGroups != null)
				for (DegreeGroupInterface g: iGroups) {
					if (g.hasCourse(rc)) return g.isCourseCritical(rc);
				}
			return false;
		}
	}

	public static class DegreeCourseInterface extends DegreeMultiSelectionInterface {
		private static final long serialVersionUID = 1L;
		private Long iCourseId = null;
		private String iSubject, iCourse, iTitle;
		private Boolean iSelected = null;
		private List<CourseAssignment> iCourses;
		private Boolean iCritical = null;

		public DegreeCourseInterface() {}

		public String getSubject() { return iSubject; }
		public void setSubject(String subject) { iSubject = subject; }
		public String getCourse() { return iCourse; }
		public void setCourse(String course) { iCourse = course; }
		public boolean hasTitle() { return iTitle != null && !iTitle.isEmpty(); }
		public String getTitle() { return iTitle; }
		public void setTitle(String title) { iTitle = title; }
		public boolean hasSelected() { return iSelected != null; }
		public boolean isSelected() { return iSelected == null || iSelected.booleanValue(); }
		public void setSelected(boolean selected) { iSelected = selected; }
		public Long getCourseId() { return iCourseId; }
		public void setCourseId(Long courseId) { iCourseId = courseId; }
		public boolean hasCritical() { return iCritical != null; }
		public boolean isCritical() { return iCritical != null && iCritical.booleanValue(); }
		public void setCritical(boolean critical) { iCritical = critical; }

		public String getCourseName() {
			return (getSubject() == null ? "" : getSubject() + " ") + getCourse();
		}
		public String getCourseNameWithTitle() {
			return hasTitle() ? getSubject() + " " + getCourse() + " - " + getTitle() : getSubject() + " " + getCourse();
		}

		public boolean hasCourses() { return iCourses != null && !iCourses.isEmpty(); }
		public boolean hasMultipleCourses() { return iCourses != null && iCourses.size() > 1; }
		public List<CourseAssignment> getCourses() { return iCourses; }
		public void addCourse(CourseAssignment course) {
			if (iCourses == null) iCourses = new ArrayList<CourseAssignment>();
			iCourses.add(course);
		}
		public boolean hasCourse(Long courseId) {
			if (hasCourses())
				for (CourseAssignment ca: getCourses())
					if (ca.getCourseId().equals(courseId)) return true;
			return false;
		}

		public boolean hasCourse(String courseId) {
			if (getId().equals(courseId)) return true;
			if (hasCourses())
				for (CourseAssignment ca: getCourses())
					if ((getId() + ":" + ca.getCourseId()).equals(courseId)) return true;
			return false;
		}

		public CourseAssignment getCourse(String courseId) {
			if (hasCourses())
				for (CourseAssignment ca: getCourses())
					if ((getId() + ":" + ca.getCourseId()).equals(courseId)) return ca;
			return null;
		}

		public CourseAssignment getSelectedCourse(boolean pickOneWhenNoneSelected) {
			if (iCourses != null && iCourseId != null)
				for (CourseAssignment course: iCourses)
					if (iCourseId.equals(course.getCourseId())) return course;
			if (pickOneWhenNoneSelected && iCourses != null && !iCourses.isEmpty()) {
				for (CourseAssignment course: iCourses)
					if (getCourseName().equals(course.getCourseName())) return course;
				int bestAv = 0;
				CourseAssignment best = null;
				for (CourseAssignment course: iCourses) {
					int av = AvailabilityComparator.value(course);
					if (best == null || av > bestAv) {
						best = course;
						bestAv = av;
					}
				}
				return (best != null ? best : iCourses.get(0));
			}
			return null;
		}

		@Override
		public String toString() {
			CourseAssignment ca = getSelectedCourse(false);
			if (ca != null) return ca.getCourseName();
			return getCourseName();
		}
	}

	public static class DegreePlaceHolderInterface extends DegreeItemInterface {
		private static final long serialVersionUID = 1L;
		private String iType;
		private String iName;

		public DegreePlaceHolderInterface() {}

		public String getName() { return iName; }
		public void setName(String name) { iName = name; }
		public String getType() { return iType; }
		public void setType(String type) { iType = type; }

		@Override
		public String toString() { return iType; }
	}

	public static class AvailabilityComparator implements Comparator<DegreeCourseInterface> {

		protected static int value(CourseAssignment ca) {
			int av = 0;
			if (ca.getLimit() != null)
				av += 4 * (ca.getLimit() < 0 ? 9999 : ca.getLimit());
			if (ca.getEnrollment() != null)
				av -= 3 * ca.getEnrollment();
			if (ca.getProjected() != null)
				av -= ca.getProjected();
			return av;
		}

		@Override
		public int compare(DegreeCourseInterface c1, DegreeCourseInterface c2) {
			CourseAssignment ca1 = c1.getSelectedCourse(true);
			CourseAssignment ca2 = c2.getSelectedCourse(true);
			int av1 = (ca1 == null ? -1 : value(ca1));
			int av2 = (ca2 == null ? -1 : value(ca2));
			return (av1 > av2 ? -1 : av1 < av2 ? 1 : c1.getCourseName().compareTo(c2.getCourseName()));
		}

	}
}


text:
/******************************************************************************
 *                    _   _____   __________                                  *
 *                   | | / / _ | / __/_  __/     Visibility                   *
 *                   | |/ / __ |_\ \  / /          Across                     *
 *                   |___/_/ |_/___/ /_/       Space and Time                 *
 *                                                                            *
 * This file is part of VAST. It is subject to the license terms in the       *
 * LICENSE file found in the top-level directory of this distribution and at  *
 * http://vast.io/license. No part of VAST, including this file, may be       *
 * copied, modified, propagated, or distributed except according to the terms *
 * contained in the LICENSE file.                                             *
 ******************************************************************************/

#pragma once

#include <chrono>
#include <deque>
#include <memory>
#include <unordered_map>

#include "vast/aliases.hpp"
#include "vast/expression.hpp"
#include "vast/ids.hpp"
#include "vast/query_options.hpp"
#include "vast/uuid.hpp"

#include "vast/system/accountant.hpp"
#include "vast/system/archive.hpp"
#include "vast/system/query_status.hpp"

namespace vast::system {

struct exporter_state {
  /// -- constants -------------------------------------------------------------

  static inline const char* name = "exporter";

  // -- properties -------------------------------------------------------------

  caf::settings status();

  // -- member variables -------------------------------------------------------

  /// Stores a handle to the ARCHIVE for fetching candidates.
  archive_type archive;

  /// Stores a handle to the INDEX for querying results.
  caf::actor index;

  /// Stores a handle to the SINK that processes results.
  caf::actor sink;

  /// Stores a handle to the STATISTICS_SUBSCRIBER that periodically prints
  /// statistics.
  caf::actor statistics_subscriber;

  /// Stores a handle to the ACCOUNTANT that collects various statistics.
  accountant_type accountant;

  /// Stores hits from the INDEX.
  ids hits;

  /// Caches tailored candidate checkers.
  std::unordered_map<type, expression> checkers;

  /// Caches results for the SINK.
  std::vector<table_slice_ptr> results;

  /// Stores the time point for when this actor got started via 'run'.
  std::chrono::steady_clock::time_point start;

  /// Stores various meta information about the progress we made on the query.
  query_status query;

  /// Stores flags for the query for distinguishing historic and continuous
  /// queries.
  query_options options;

  /// Stores the query ID we receive from the INDEX.
  uuid id;

  /// Stores the user-defined export query.
  expression expr;
};

/// The EXPORTER receives index hits, looks up the corresponding events in the
/// archive, and performs a candidate check to select the resulting stream of
/// matching events.
/// @param self The actor handle.
/// @param ast The AST of query.
/// @param qos The query options.
caf::behavior exporter(caf::stateful_actor<exporter_state>* self,
                       expression expr, query_options opts);

} // namespace vast::system

redpajama_set_name: RedPajamaGithub


text:
// Generated by xsd compiler for android/java
// DO NOT CHANGE!
package com.ebay.trading.api;

import java.io.Serializable;
import com.leansoft.nano.annotation.*;

/**
 *
 *   Type defining the SupportedSellerProfile container, which contains
 * summary information related to specific Business Policies payment, return policy, and shipping
 * profiles. The profile type is found in the ProfileType field.
 *

 * Note:
 * Business Policies are not yet available for use on the eBay platform.
 *
 *
 */
public class SupportedSellerProfileType implements Serializable {

    private static final long serialVersionUID = -1L;

	@Element(name = "ProfileID")
	@Order(value=0)
	public Long profileID;

	@Element(name = "ProfileType")
	@Order(value=1)
	public String profileType;

	@Element(name = "ProfileName")
	@Order(value=2)
	public String profileName;

	@Element(name = "ShortSummary")
	@Order(value=3)
	public String shortSummary;

	@Element(name = "CategoryGroup")
	@Order(value=4)
	public CategoryGroupType categoryGroup;

}


text:
require 'fog/openstack/models/model'

module Fog
  module Monitoring
    class OpenStack
      class NotificationMethod < Fog::OpenStack::Model
        identity :id

        attribute :name
        attribute :links
        attribute :type
        attribute :address
        attribute :period

        def create
          requires :name, :type, :address
          merge_attributes(
            service.create_notification_method(attributes).body
          )
        end

        def update(attr = nil)
          requires :name, :type, :address
          merge_attributes(
            service.put_notification_method(id, attr || attributes).body
          )
        end

        def patch(attr = nil)
          merge_attributes(
            service.patch_notification_method(id, attr || attributes).body
          )
        end

        def destroy
          requires :id
          service.delete_notification_method(id)
          true
        end

        def to_s
          name
        end
      end
    end
  end
end


text:
<?php

/**
 * (c) 2011 - ∞ Vespolina Project http://www.vespolina-project.org
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Vespolina\Invoice\Manager;

/**
 * @author Richard Shank <develop@zestic.com>
 */
use Vespolina\Entity\Partner\PartnerInterface;
use Vespolina\Entity\Invoice\InvoiceInterface;

interface InvoiceManagerInterface
{
    /**
     * Create a new invoice for the given type
     * @return \Vespolina\Entity\Invoice\InvoiceInterface
     */
    function createInvoice($type = 'default');

    /**
     * Create an invoice item
     *
     * @return mixed
     */
    function createInvoiceItem();

    /**
     * @param $id
     * @return \Vespolina\Entity\Invoice\InvoiceInterface
     */
    function findById($id);

    function findAll();

    /**
     * @param \Vespolina\Entity\Partner\Partner $partner
     * @return array
     */
    function findAllInvoicesByPartner(PartnerInterface $partner);

    /**
     * @param \Vespolina\Entity\Partner\Partner $partner
     * @return InvoiceInterface
     */
    function findLastInvoiceByPartner(PartnerInterface $partner);

    /**
     * @param \Vespolina\Entity\Partner\Partner $partner
     * @param \DateTime $periodStart
     * @param \DateTime $periodEnd
     * @return array
     */
    function findInvoicesByPartnerAndPeriod(PartnerInterface $partner, $periodStart, $periodEnd);

    /**
     * Update and persist the invoice
     *
     * @param \Vespolina\Entity\Invoice\InvoiceInterface $invoice
     * @param Boolean $andFlush Whether to flush the changes (default true)
     */
    function updateInvoice(InvoiceInterface $invoice, $andPersist = true);
}


text:
import static org.junit.Assert.assertEquals;
import net.sourceforge.mayfly.Database;
import net.sourceforge.mayfly.dump.SqlDumper;

import org.junit.Test;

public class DumperTest {

    @Test public void upgrade() {
        Database upgrader = new Database();
        upgrader.execute("create table foo(x integer)");
        upgrader.execute("alter table foo add column name varchar(255)");

        Database freshInstall = new Database();
        freshInstall.execute("create table foo(x integer, name varchar(255))");

        assertEquals(
            new SqlDumper().dump(freshInstall.dataStore()),
            new SqlDumper().dump(upgrader.dataStore()));

        // Now demonstrate a failure
//        freshInstall.execute("create table other(y integer)");
//        assertEquals(
//            new SqlDumper().dump(freshInstall.dataStore()),
//            new SqlDumper().dump(upgrader.dataStore()));
   }

}


text:
@extends('layouts.admin')

  @section('content')

    <?php $message=Session::get('message')?>

    @if($message == 'deleted')

      <button type="button" class="close" data-dismiss="alert" aria-label="Close">×</button>
      Correo eliminado

    @endif

    @if($message == 'sended')

      <button type="button" class="close" data-dismiss="alert" aria-label="Close">×</button>
      Correo enviado

    @endif

    @if($message == 'assigned_permission')

      <button type="button" class="close" data-dismiss="alert" aria-label="Close">×</button>
      Permisos registrados

    @endif

Menu de permisos

Asignar permisos

Enviar correos de alertas

Alertas de correo

Buzon de correo

Correo

@stop


text:
<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Post
 *
 * @ORM\Table(name="post")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\PostRepository")
 */
class Post
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255)
     */
    private $name;

    /**
     * @var string
     *
     * @ORM\Column(name="description", type="string", length=255)
     */
    private $description;

    /**
     * Get id
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param string $name
     *
     * @return Post
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set description
     *
     * @param string $description
     *
     * @return Post
     */
    public function setDescription($description)
    {
        $this->description = $description;

        return $this;
    }

    /**
     * Get description
     *
     * @return string
     */
    public function getDescription()
    {
        return $this->description;
    }
}


text:
package esg.orp.app;

import java.io.IOException;
import java.net.URLEncoder;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import esg.orp.Parameters;
import esg.orp.Utils;

/**
 * Filter that redirects the request to a registration relay page,
 * if the request has not been already authorized by the upstream filter chain.
 * This filter must come LAST in the access control filter chain
 * because it redirects the browser to another web application.
 *
 * Requires the mandatory filter configuration parameter "registrationRelayUrl".
 *
 * @author Luca Cinquini
 *
 */
public class RegistrationFilter implements Filter {

    private final Log LOG = LogFactory.getLog(this.getClass());

    private FilterConfig filterConfig;

    private String registrationRelayUrl;

    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {

        final HttpServletRequest req = (HttpServletRequest)request;
        final HttpServletResponse resp = (HttpServletResponse)response;
        final Boolean authzAtt = (Boolean)request.getAttribute(Parameters.AUTHORIZATION_REQUEST_ATTRIBUTE);
        if (LOG.isDebugEnabled()) LOG.debug("RegistrationFilter: current authorization attribute="+authzAtt);

        // request not authorized > redirect to registration relay page
        if (authzAtt==null || authzAtt.booleanValue()==false) {

            if (!response.isCommitted()) {
                //String url = Utils.transformUrl( Utils.getFullRequestUrl(req) );
                String url = Utils.getFullRequestUrl(req);
                final String redirectUrl = registrationRelayUrl+ "?"+Parameters.HTTP_PARAMETER_RESOURCE+"="+URLEncoder.encode(url,"UTF-8");
                if (LOG.isDebugEnabled()) LOG.debug("Redirecting to: "+redirectUrl);
                resp.sendRedirect(redirectUrl);
            }

        // request authorized > keep processing
        } else {
            chain.doFilter(request, response);
        }
    }

    public void init(final FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
        this.registrationRelayUrl = filterConfig.getInitParameter(Parameters.REGISTRATION_RELAY_URL);
        Assert.isTrue(StringUtils.hasText(this.registrationRelayUrl), "Missing filter configuration parameter: "+Parameters.REGISTRATION_RELAY_URL);
    }

    public void destroy() { this.filterConfig = null; }

}


text:
import { VNode } from "../src";
import { createDom } from "../src/projection";
import { expect, sinon } from "./test-utilities";

describe("Projection", () => {
  describe("createDom", () => {
    let vnode: VNode;
    let parentNode: Node;

    beforeEach(() => {
      vnode = {
        vnodeSelector: "",
        properties: undefined,
        children: undefined,
        domNode: undefined,
        text: undefined,
      };

      parentNode = <any>{
        ownerDocument: <any>{
          createTextNode: sinon.stub().returns({}),
        },
        insertBefore: sinon.stub(),
        appendChild: sinon.stub(),
      };
    });

    it("when creating a text node and the vnode already has a (cached) domNode re-use and update the domNode", () => {
      let domNode = <any>{
        nodeValue: "Bar",
      };
      Object.assign(vnode, { text: "Foo", domNode });

      createDom(vnode, parentNode, undefined, {});

      expect(parentNode.ownerDocument.createTextNode).to.not.have.been.called;
      expect(domNode.nodeValue).to.equal("Foo");
    });
  });
});


text:
/**
 * Service discovery classes.
 */
package co.cask.cdap.common.discovery;


text:
package com.github.devconslejme.misc.jme;

import com.github.devconslejme.misc.GlobalManagerI;
import com.github.devconslejme.misc.SystemAlertI;
import com.jme3.scene.Spatial;

/**
 * TODO implement a JME only alert, it currently will only work with lemur
 * @author Henrique Abdalla <https://github.com/AquariusPower><https://sourceforge.net/u/teike/profile/>
 */
public class SystemAlertJmeI extends SystemAlertI {
	public static SystemAlertJmeI i(){return GlobalManagerI.i().retrieveOverridingSupers(SystemAlertJmeI.class, null, SystemAlertI.class);}

	private Spatial	sptAlert;

	public void configure(){
//		if(!GlobalManagerI.i().isSet(SystemAlertI.class)){
//			GlobalManagerI.i().putGlobal(SystemAlertI.class, this); //overrides global
//		}
	}

	public Spatial getAlertSpatial() {
		return sptAlert;
	}

	protected void setAlertSpatial(Spatial spt){
		this.sptAlert=spt;
	}

	@Override
	public Spatial getActionSourceElement() {
		return (Spatial)super.getActionSourceElement();
	}

	@Override
	public StackTraceElement[] showSystemAlert(String strMsg, Object objActionSourceElement) {
		assert(objActionSourceElement==null || Spatial.class.isInstance(objActionSourceElement));
		return super.showSystemAlert(strMsg, objActionSourceElement);
	}

//	@Override
//	protected void captureUserInput() {
//		QueueI.i().enqueue(new CallableXAnon() {
//			@Override
//			public Boolean call() {
//				update(getTPF());
//				return true;
//			}
//		});
//	}

//	@Override
//	protected boolean updateCapture(float fTPF, CallableX cx) {
//		// TODO Auto-generated method stub
//		throw new UnsupportedOperationException("method not implemented");
//		return super.updateCapture(fTPF, cx);
//	}

//	private void update(float tpf) {
//		//TODO do something here
//	}
}


text:
//
//  LJFoodCircleViewController.h
//  meishimeike
//
//  Created by zhenglong duan on 11/02/2017.
//  Copyright © 2017 zhenglong duan. All rights reserved.
//

#import "LJBaseViewController.h"

@interface LJShoppingCartViewController : LJBaseViewController
/*** 结算按钮 ***/
@property (nonatomic,strong) UIButton *settlementBtn;
/*** 金额L ***/
@property (nonatomic,strong) UILabel *priceLabel;
/*** 暂时计算的金额 ***/
@property (nonatomic,assign) CGFloat Allprice;
/*** 最终要结算的金额 ***/
@property (nonatomic,assign) CGFloat totalPrice;
/*** 合计一系列背景 ***/
@property (nonatomic,strong) UIView *view1;
/*** 底部标签栏 ***/
@property (nonatomic,strong) UIView *bottomViewBg;
/*** 是否从商品详情页面进入 ***/
@property (nonatomic,assign) BOOL isOtherPage;

@end


text:
<?php

namespace AuthBucket\OAuth2\Symfony\Component\Validator\Constraints;

use Symfony\Component\Validator\Constraints\Regex;

/**
 * @Annotation
 *
 * @author Wong Hoi Sing Edison <hswong3i@pantarei-design.com>
 */
class ClientId extends Regex
{
    public function __construct($options = null)
    {
        return parent::__construct(array_merge([
            'message' => 'This is not a valid client_id.',
            'pattern' => '/^([\x20-\x7E]*)$/',
        ], (array) $options));
    }
}


text:
package org.wso2.andes.server.logging.subjects;

import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.wso2.andes.framing.AMQShortString;
import org.wso2.andes.server.configuration.ServerConfiguration;
import org.wso2.andes.server.exchange.Exchange;
import org.wso2.andes.server.logging.LogActor;
import org.wso2.andes.server.logging.LogMessage;
import org.wso2.andes.server.logging.LogSubject;
import org.wso2.andes.server.logging.UnitTestMessageLogger;
import org.wso2.andes.server.logging.actors.TestLogActor;
import org.wso2.andes.server.queue.AMQQueue;
import org.wso2.andes.server.util.InternalBrokerBaseCase;
import org.wso2.andes.server.virtualhost.VirtualHost;

import java.util.List;

/**
 * Abstract Test for LogSubject testing
 * Includes common validation code and two common tests.
 *
 * Each test class sets up the LogSubject and contains details of how to
 * validate this class then performs a log statement with logging enabled and
 * logging disabled.
 *
 * The resulting log file is then validated.
 *
 */
public abstract class AbstractTestLogSubject extends InternalBrokerBaseCase
{
    protected Configuration _config = new PropertiesConfiguration();
    protected LogSubject _subject = null;

    @Override
    public void setUp() throws Exception
    {
        super.setUp();

        _config.setProperty(ServerConfiguration.STATUS_UPDATES, "ON");
    }

    protected List<Object> performLog() throws ConfigurationException
    {
        if (_subject == null)
        {
            throw new NullPointerException("LogSubject has not been set");
        }

        ServerConfiguration serverConfig = new ServerConfiguration(_config);
        UnitTestMessageLogger logger = new UnitTestMessageLogger(serverConfig);

        LogActor actor = new TestLogActor(logger);

        actor.message(_subject, new LogMessage()
        {
            public String toString()
            {
                return "<Log Message>";
            }

            public String getLogHierarchy()
            {
                return "test.hierarchy";
            }
        });

        return logger.getLogMessages();
    }

    /**
     * Verify that the connection section has the expected items
     *
     * @param connectionID - The connection id (int) to check for
     * @param user         - the Connected username
     * @param ipString     - the ipString/hostname
     * @param vhost        - the virtualhost that the user connected to.
     * @param message      - the message these values should appear in.
     */
    protected void verifyConnection(long connectionID, String user, String ipString, String vhost, String message)
    {
        // This should return us MockProtocolSessionUser@null/test
        String connectionSlice = getSlice("con:" + connectionID, message);

        assertNotNull("Unable to find connection 'con:" + connectionID + "'",
                      connectionSlice);

        // Exract the userName
        String[] userNameParts = connectionSlice.split("@");

        assertEquals("Unable to split Username from rest of Connection:"
                     + connectionSlice, 2, userNameParts.length);

        assertEquals("Username not as expected", userNameParts[0], user);

        // Extract IP.
        // The connection will be of the format - guest@/127.0.0.1:1/test
        // and so our userNamePart will be '/127.0.0.1:1/test'
        String[] ipParts = userNameParts[1].split("/");

        // We will have three sections
        assertEquals("Unable to split IP from rest of Connection:"
                     + userNameParts[1], 3, ipParts.length);

        // We need to skip the first '/' split will be empty so validate 1 as IP
        assertEquals("IP not as expected", ipString, ipParts[1]);

        //Finally check vhost which is section 2
        assertEquals("Virtualhost name not as expected.", vhost, ipParts[2]);
    }

    /**
     * Verify that the RoutingKey is present in the provided message.
     *
     * @param message    The message to check
     * @param routingKey The routing key to check against
     */
    protected void verifyRoutingKey(String message, AMQShortString routingKey)
    {
        String routingKeySlice = getSlice("rk", message);

        assertNotNull("Routing Key not found:" + message, routingKey);

        assertEquals("Routing key not correct",
                     routingKey.toString(), routingKeySlice);
    }

    /**
     * Verify that the given Queue's name exists in the provided message
     *
     * @param message The message to check
     * @param queue   The queue to check against
     */
    protected void verifyQueue(String message, AMQQueue queue)
    {
        String queueSlice = getSlice("qu", message);

        assertNotNull("Queue not found:" + message, queueSlice);

        assertEquals("Queue name not correct",
                     queue.getNameShortString().toString(), queueSlice);
    }

    /**
     * Verify that the given exchange (name and type) are present in the
     * provided message.
     *
     * @param message  The message to check
     * @param exchange the exchange to check against
     */
    protected void verifyExchange(String message, Exchange exchange)
    {
        String exchangeSilce = getSlice("ex", message);

        assertNotNull("Exchange not found:" + message, exchangeSilce);

        String[] exchangeParts = exchangeSilce.split("/");

        assertEquals("Exchange should be in two parts ex(type/name)", 2,
                     exchangeParts.length);

        assertEquals("Exchange type not correct",
                     exchange.getTypeShortString().toString(), exchangeParts[0]);

        assertEquals("Exchange name not correct",
                     exchange.getNameShortString().toString(), exchangeParts[1]);

    }

    /**
     * Verify that a VirtualHost with the given name appears in the given
     * message.
     *
     * @param message the message to search
     * @param vhost   the vhostName to check against
     */
    static public void verifyVirtualHost(String message, VirtualHost vhost)
    {
        String vhostSlice = getSlice("vh", message);

        assertNotNull("Virtualhost not found:" + message, vhostSlice);

        assertEquals("Virtualhost not correct", "/" + vhost.getName(), vhostSlice);
    }

    /**
     * Parse the log message and return the slice according to the following:
     * Given Example:
     * con:1(guest@127.0.0.1/test)/ch:2/ex(amq.direct)/qu(myQueue)/rk(myQueue)
     *
     * Each item (except channel) is of the format <key>(<values>)
     *
     * So Given an ID to slice on:
     * con:1 - Connection 1
     * ex - exchange
     * qu - queue
     * rk - routing key
     *
     * @param sliceID the slice to locate
     * @param message the message to search in
     *
     * @return the slice if found otherwise null is returned
     */
    static public String getSlice(String sliceID, String message)
    {
        int indexOfSlice = message.indexOf(sliceID + "(");

        if (indexOfSlice == -1)
        {
            return null;
        }

        int endIndex = message.indexOf(')', indexOfSlice);

        if (endIndex == -1)
        {
            return null;
        }

        return message.substring(indexOfSlice + 1 + sliceID.length(),
                                 endIndex);
    }

    /**
     * Test that when Logging occurs a single log statement is provided
     *
     * @throws ConfigurationException
     */
    public void testEnabled() throws ConfigurationException
    {
        List<Object> logs = performLog();

        assertEquals("Log has incorrect message count", 1, logs.size());

        validateLogStatement(String.valueOf(logs.get(0)));
    }

    /**
     * Call to the individiual tests to validate the message is formatted as
     * expected
     *
     * @param message the message whos format needs validation
     */
    protected abstract void validateLogStatement(String message);

    /**
     * Ensure that when status-updates are off this does not perform logging
     *
     * @throws ConfigurationException
     */
    public void testDisabled() throws ConfigurationException
    {
        _config.setProperty(ServerConfiguration.STATUS_UPDATES, "OFF");

        List<Object> logs = performLog();

        assertEquals("Log has incorrect message count", 0, logs.size());
    }

}


text:
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ProjectModuleManager">
    <modules>
      <module fileurl="file://$PROJECT_DIR$/ptoPlanner.iml" filepath="$PROJECT_DIR$/ptoPlanner.iml" />
    </modules>
  </component>
</project>


text:
#import "SVGKPointsAndPathsParser.h"

#import "NSCharacterSet+SVGKExtensions.h"

// TODO: support quadratic-bezier-curveto
// TODO: support smooth-quadratic-bezier-curveto
// TODO: support elliptical-arc

inline SVGCurve SVGCurveMake(CGFloat cx1, CGFloat cy1, CGFloat cx2, CGFloat cy2, CGFloat px, CGFloat py)
{
    SVGCurve curve;
    curve.c1 = CGPointMake(cx1, cy1);
    curve.c2 = CGPointMake(cx2, cy2);
    curve.p = CGPointMake(px, py);
    return curve;
}

inline BOOL SVGCurveEqualToCurve(SVGCurve curve1, SVGCurve curve2)
{
    return (
            CGPointEqualToPoint(curve1.c1, curve2.c1)
            &&
            CGPointEqualToPoint(curve1.c2, curve2.c2)
            &&
            CGPointEqualToPoint(curve1.p, curve2.p)
            );
}

@implementation SVGKPointsAndPathsParser

/* references
 http://www.w3.org/TR/2011/REC-SVG11-20110816/paths.html#PathDataBNF
 http://www.w3.org/TR/2011/REC-SVG11-20110816/shapes.html#PointsBNF

 */

/*
 http://www.w3.org/TR/2011/REC-SVG11-20110816/paths.html#PathDataBNF
 svg-path:
 wsp* moveto-drawto-command-groups? wsp*
 moveto-drawto-command-groups:
 moveto-drawto-command-group
 | moveto-drawto-command-group wsp* moveto-drawto-command-groups
 moveto-drawto-command-group:
 moveto wsp* drawto-commands?
 drawto-commands:
 drawto-command
 | drawto-command wsp* drawto-commands
 drawto-command:
 closepath
 | lineto
 | horizontal-lineto
 | vertical-lineto
 | curveto
 | smooth-curveto
 | quadratic-bezier-curveto
 | smooth-quadratic-bezier-curveto
 | elliptical-arc
 moveto:
 ( "M" | "m" ) wsp* moveto-argument-sequence
 moveto-argument-sequence:
 coordinate-pair
 | coordinate-pair comma-wsp? lineto-argument-sequence
 closepath:
 ("Z" | "z")
 lineto:
 ( "L" | "l" ) wsp* lineto-argument-sequence
 lineto-argument-sequence:
 coordinate-pair
 | coordinate-pair comma-wsp? lineto-argument-sequence
 horizontal-lineto:
 ( "H" | "h" ) wsp* horizontal-lineto-argument-sequence
 horizontal-lineto-argument-sequence:
 coordinate
 | coordinate comma-wsp? horizontal-lineto-argument-sequence
 vertical-lineto:
 ( "V" | "v" ) wsp* vertical-lineto-argument-sequence
 vertical-lineto-argument-sequence:
 coordinate
 | coordinate comma-wsp? vertical-lineto-argument-sequence
 curveto:
 ( "C" | "c" ) wsp* curveto-argument-sequence
 curveto-argument-sequence:
 curveto-argument
 | curveto-argument comma-wsp? curveto-argument-sequence
 curveto-argument:
 coordinate-pair comma-wsp? coordinate-pair comma-wsp? coordinate-pair
 smooth-curveto:
 ( "S" | "s" ) wsp* smooth-curveto-argument-sequence
 smooth-curveto-argument-sequence:
 smooth-curveto-argument
 | smooth-curveto-argument comma-wsp? smooth-curveto-argument-sequence
 smooth-curveto-argument:
 coordinate-pair comma-wsp? coordinate-pair
 quadratic-bezier-curveto:
 ( "Q" | "q" ) wsp* quadratic-bezier-curveto-argument-sequence
 quadratic-bezier-curveto-argument-sequence:
 quadratic-bezier-curveto-argument
 | quadratic-bezier-curveto-argument comma-wsp?
 quadratic-bezier-curveto-argument-sequence
 quadratic-bezier-curveto-argument:
 coordinate-pair comma-wsp? coordinate-pair
 smooth-quadratic-bezier-curveto:
 ( "T" | "t" ) wsp* smooth-quadratic-bezier-curveto-argument-sequence
 smooth-quadratic-bezier-curveto-argument-sequence:
 coordinate-pair
 | coordinate-pair comma-wsp? smooth-quadratic-bezier-curveto-argument-sequence
 elliptical-arc:
 ( "A" | "a" ) wsp* elliptical-arc-argument-sequence
 elliptical-arc-argument-sequence:
 elliptical-arc-argument
 | elliptical-arc-argument comma-wsp? elliptical-arc-argument-sequence
 elliptical-arc-argument:
 nonnegative-number comma-wsp? nonnegative-number comma-wsp?
 number comma-wsp flag comma-wsp? flag comma-wsp? coordinate-pair
 coordinate-pair:
 coordinate comma-wsp? coordinate
 coordinate:
 number
 nonnegative-number:
 integer-constant
 | floating-point-constant
 number:
 sign? integer-constant
 | sign? floating-point-constant
 flag:
 "0" | "1"
 comma-wsp:
 (wsp+ comma? wsp*) | (comma wsp*)
 comma:
 ","
 integer-constant:
 digit-sequence
 floating-point-constant:
 fractional-constant exponent?
 | digit-sequence exponent
 fractional-constant:
 digit-sequence? "." digit-sequence
 | digit-sequence "."
 exponent:
 ( "e" | "E" ) sign? digit-sequence
 sign:
 "+" | "-"
 digit-sequence:
 digit
 | digit digit-sequence
 digit:
 "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
 */

/*
 http://www.w3.org/TR/2011/REC-SVG11-20110816/shapes.html#PointsBNF

 list-of-points:
 wsp* coordinate-pairs? wsp*
 coordinate-pairs:
 coordinate-pair
 | coordinate-pair comma-wsp coordinate-pairs
 coordinate-pair:
 coordinate comma-wsp coordinate
 | coordinate negative-coordinate
 coordinate:
 number
 number:
 sign? integer-constant
 | sign? floating-point-constant
 negative-coordinate:
 "-" integer-constant
 | "-" floating-point-constant
 comma-wsp:
 (wsp+ comma? wsp*) | (comma wsp*)
 comma:
 ","
 integer-constant:
 digit-sequence
 floating-point-constant:
 fractional-constant exponent?
 | digit-sequence exponent
 fractional-constant:
 digit-sequence? "." digit-sequence
 | digit-sequence "."
 exponent:
 ( "e" | "E" ) sign? digit-sequence
 sign:
 "+" | "-"
 digit-sequence:
 digit
 | digit digit-sequence
 digit:
 "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
 */

/**
 wsp:
 (#x20 | #x9 | #xD | #xA)
 */
+ (void) readWhitespace:(NSScanner*)scanner
{
	/** This log message can be called literally hundreds of thousands of times in a single parse, which defeats
	 even Cocoa Lumberjack.

	 Even in "verbose" debugging, that's too much!

	 Hence: commented-out
	DDLogVerbose(@"Apple's implementation of scanCharactersFromSet seems to generate large amounts of temporary objects and can cause a crash here by taking literally megabytes of RAM in temporary internal variables. This is surprising, but I can't see anythign we're doing wrong. Adding this autoreleasepool drops memory usage (inside Apple's methods!) massively, so it seems to be the right thing to do");
	 */
	@autoreleasepool
	{
		[scanner scanCharactersFromSet:[NSCharacterSet SVGWhitespaceCharacterSet]
                        intoString:NULL];
	}
}

+ (void) readCommaAndWhitespace:(NSScanner*)scanner
{
    [SVGKPointsAndPathsParser readWhitespace:scanner];
    static NSString* comma = @",";
    [scanner scanString:comma intoString:NULL];
    [SVGKPointsAndPathsParser readWhitespace:scanner];
}

/**
 moveto-drawto-command-groups:
 moveto-drawto-command-group
 | moveto-drawto-command-group wsp* moveto-drawto-command-groups
 */
+ (CGPoint) readMovetoDrawtoCommandGroups:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
{
#if VERBOSE_PARSE_SVG_COMMAND_STRINGS
	DDLogVerbose(@"Parsing command string: move-to, draw-to command");
#endif
    CGPoint lastCoord = [SVGKPointsAndPathsParser readMovetoDrawto:scanner path:path relativeTo:origin isRelative:isRelative];
    [SVGKPointsAndPathsParser readWhitespace:scanner];

    while (![scanner isAtEnd])
	{
        [SVGKPointsAndPathsParser readWhitespace:scanner];
		/** FIXME: wasn't originally, but maybe should be:

		 origin = isRelative ? lastCoord : origin;
		 */
        lastCoord = [SVGKPointsAndPathsParser readMovetoDrawto:scanner path:path relativeTo:origin isRelative:isRelative];
    }

	return lastCoord;
}

/** moveto-drawto-command-group:
 moveto wsp* drawto-commands?
 */
+ (CGPoint) readMovetoDrawto:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
{
    CGPoint lastMove = [SVGKPointsAndPathsParser readMoveto:scanner path:path relativeTo:origin isRelative:isRelative];
    [SVGKPointsAndPathsParser readWhitespace:scanner];
    return lastMove;
}

/**
 moveto:
 ( "M" | "m" ) wsp* moveto-argument-sequence
 */
+ (CGPoint) readMoveto:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
{
    NSString* cmd;
    NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Mm"];
    if( ! [scanner scanCharactersFromSet:cmdFormat intoString:&cmd] )
	{
		NSAssert(FALSE, @"failed to scan move to command");
		return origin;
	}

    [SVGKPointsAndPathsParser readWhitespace:scanner];

    CGPoint lastCoordinate = [SVGKPointsAndPathsParser readMovetoArgumentSequence:scanner path:path relativeTo:origin isRelative:isRelative];
    return lastCoordinate;
}

/** moveto-argument-sequence:
 coordinate-pair
 | coordinate-pair comma-wsp? lineto-argument-sequence
 */
+ (CGPoint) readMovetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
{
    CGPoint coord = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
    coord.x += origin.x;
	coord.y += origin.y;

    CGPathMoveToPoint(path, NULL, coord.x, coord.y);
#if DEBUG_PATH_CREATION
	DDLogWarn(@"[%@] PATH: MOVED to %2.2f, %2.2f", [SVGKPointsAndPathsParser class], coord.x, coord.y );
#endif

    [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];

    if (![scanner isAtEnd]) {
        coord = [SVGKPointsAndPathsParser readLinetoArgumentSequence:scanner path:path relativeTo:(isRelative)?coord:origin isRelative:isRelative];
    }

    return coord;
}

/**
 coordinate-pair:
 coordinate comma-wsp? coordinate
 */

+ (CGPoint) readCoordinatePair:(NSScanner*)scanner
{
	CGPoint p;
	[SVGKPointsAndPathsParser readCoordinate:scanner intoFloat:&p.x];
    [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];
    [SVGKPointsAndPathsParser readCoordinate:scanner intoFloat:&p.y];

    return p;
}

+ (void) readCoordinate:(NSScanner*)scanner intoFloat:(CGFloat*) floatPointer
{
#if CGFLOAT_IS_DOUBLE
	if( ! [scanner scanDouble:floatPointer] )
		NSAssert(FALSE, @"invalid coord");
#else
	if( ! [scanner scanFloat:floatPointer] )
		NSAssert(FALSE, @"invalid coord");
#endif
}

/**
 lineto:
 ( "L" | "l" ) wsp* lineto-argument-sequence
 */
+ (CGPoint) readLinetoCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
{
#if VERBOSE_PARSE_SVG_COMMAND_STRINGS
	DDLogVerbose(@"Parsing command string: line-to command");
#endif

    NSString* cmd;
    NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Ll"];

	if( ! [scanner scanCharactersFromSet:cmdFormat intoString:&cmd] )
	{
		NSAssert( FALSE, @"failed to scan line to command");
		return origin;
	}

    [SVGKPointsAndPathsParser readWhitespace:scanner];

    CGPoint lastCoordinate = [SVGKPointsAndPathsParser readLinetoArgumentSequence:scanner path:path relativeTo:origin isRelative:isRelative];
    return lastCoordinate;
}

/**
 lineto-argument-sequence:
 coordinate-pair
 | coordinate-pair comma-wsp? lineto-argument-sequence
 */
+ (CGPoint) readLinetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
{
    CGPoint p = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
    CGPoint coord = CGPointMake(p.x+origin.x, p.y+origin.y);
    CGPathAddLineToPoint(path, NULL, coord.x, coord.y);
#if DEBUG_PATH_CREATION
	DDLogWarn(@"[%@] PATH: LINE to %2.2f, %2.2f", [SVGKPointsAndPathsParser class], coord.x, coord.y );
#endif

    [SVGKPointsAndPathsParser readWhitespace:scanner];

	while( ![scanner isAtEnd])
	{
		origin = (isRelative)?coord:origin;
		p = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
		coord = CGPointMake(p.x+origin.x, p.y+origin.y);
		CGPathAddLineToPoint(path, NULL, coord.x, coord.y);
#if DEBUG_PATH_CREATION
		DDLogWarn(@"[%@] PATH: LINE to %2.2f, %2.2f", [SVGKPointsAndPathsParser class], coord.x, coord.y );
#endif

		[SVGKPointsAndPathsParser readWhitespace:scanner];
	}

    return coord;
}

/**
 quadratic-bezier-curveto:
 ( "Q" | "q" ) wsp* quadratic-bezier-curveto-argument-sequence
 */
+ (SVGCurve) readQuadraticCurvetoCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
{
#if VERBOSE_PARSE_SVG_COMMAND_STRINGS
	DDLogVerbose(@"Parsing command string: quadratic-bezier-curve-to command");
#endif

    NSString* cmd;
    NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Qq"];

	if( ! [scanner scanCharactersFromSet:cmdFormat intoString:&cmd] )
	{
		NSAssert( FALSE, @"failed to scan quadratic curve to command");
		return SVGCurveZero;
	}

    [SVGKPointsAndPathsParser readWhitespace:scanner];

    SVGCurve lastCurve = [SVGKPointsAndPathsParser readQuadraticCurvetoArgumentSequence:scanner path:path relativeTo:origin isRelative:isRelative];
    return lastCurve;
}
/**
 quadratic-bezier-curveto-argument-sequence:
 quadratic-bezier-curveto-argument
 | quadratic-bezier-curveto-argument comma-wsp? quadratic-bezier-curveto-argument-sequence
 */
+ (SVGCurve) readQuadraticCurvetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
{
    SVGCurve curve = [SVGKPointsAndPathsParser readQuadraticCurvetoArgument:scanner path:path relativeTo:origin];

	while(![scanner isAtEnd])
	{
		curve = [SVGKPointsAndPathsParser readQuadraticCurvetoArgument:scanner path:path relativeTo:(isRelative ? curve.p : origin)];
    }

    return curve;
}

/**
 quadratic-bezier-curveto-argument:
 coordinate-pair comma-wsp? coordinate-pair
 */
+ (SVGCurve) readQuadraticCurvetoArgument:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin
{
	SVGCurve curveResult;

    curveResult.c1 = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
    curveResult.c1.x += origin.x;
	curveResult.c1.y += origin.y;
    [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];

	curveResult.c2 = CGPointZero;

    curveResult.p = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
    curveResult.p.x += origin.x;
	curveResult.p.y += origin.y;
    [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];

    CGPathAddQuadCurveToPoint(path, NULL, curveResult.c1.x, curveResult.c1.y, curveResult.p.x, curveResult.p.y);
#if DEBUG_PATH_CREATION
	DDLogWarn(@"[%@] PATH: QUADRATIC CURVE to (%2.2f, %2.2f)..(%2.2f, %2.2f)", [SVGKPointsAndPathsParser class], curveResult.c1.x, curveResult.c1.y, curveResult.p.x, curveResult.p.y);
#endif

    return curveResult;
}

/**
 smooth-quadratic-bezier-curveto:
 ( "T" | "t" ) wsp* smooth-quadratic-bezier-curveto-argument-sequence
 */
+ (SVGCurve) readSmoothQuadraticCurvetoCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin withPrevCurve:(SVGCurve)prevCurve
{
#if VERBOSE_PARSE_SVG_COMMAND_STRINGS
	DDLogVerbose(@"Parsing command string: smooth-quadratic-bezier-curve-to command");
#endif
	NSString* cmd;
    NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Tt"];

	if( ! [scanner scanCharactersFromSet:cmdFormat intoString:&cmd] )
	{
		NSAssert( FALSE, @"failed to scan smooth quadratic curve to command");
		return SVGCurveZero;
	}

    [SVGKPointsAndPathsParser readWhitespace:scanner];

    SVGCurve lastCurve = [SVGKPointsAndPathsParser readSmoothQuadraticCurvetoArgumentSequence:scanner path:path relativeTo:origin withPrevCurve:prevCurve];
    return lastCurve;
}

/**
 smooth-quadratic-bezier-curveto-argument-sequence:
 smooth-quadratic-bezier-curveto-argument
 | smooth-quadratic-bezier-curveto-argument comma-wsp? smooth-quadratic-bezier-curveto-argument-sequence
 */
+ (SVGCurve) readSmoothQuadraticCurvetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin withPrevCurve:(SVGCurve)prevCurve
{
    SVGCurve curve = [SVGKPointsAndPathsParser readSmoothQuadraticCurvetoArgument:scanner path:path relativeTo:origin withPrevCurve:prevCurve];

    if (![scanner isAtEnd]) {
        curve = [SVGKPointsAndPathsParser readSmoothQuadraticCurvetoArgumentSequence:scanner path:path relativeTo:curve.p withPrevCurve:curve];
    }

    return curve;
}

/**
 smooth-quadratic-bezier-curveto-argument:
 coordinate-pair comma-wsp? coordinate-pair
 */
+ (SVGCurve) readSmoothQuadraticCurvetoArgument:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin withPrevCurve:(SVGCurve)prevCurve
{
    CGPoint p1 = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
    CGPoint coord1 = CGPointMake(p1.x+origin.x, p1.y+origin.y);
    [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];

    SVGCurve thisCurve = SVGCurveZero;
    if (SVGCurveEqualToCurve(SVGCurveZero, prevCurve)) {
        // assume control point is coincident with the current point
        thisCurve = SVGCurveMake(coord1.x, coord1.y, 0.0f, 0.0f, prevCurve.p.x, prevCurve.p.y);
    } else {
        // calculate the mirror of the previous control point
        CGPoint currentPoint = prevCurve.p;
        CGPoint controlPoint = prevCurve.c1;
        CGPoint mirrorCoord = CGPointMake(currentPoint.x+(currentPoint.x-controlPoint.x), currentPoint.y+(currentPoint.y-controlPoint.y));
        thisCurve = SVGCurveMake(mirrorCoord.x, mirrorCoord.y, 0.0f, 0.0f, coord1.x, coord1.y);
    }

    CGPathAddQuadCurveToPoint(path, NULL, thisCurve.c1.x, thisCurve.c1.y, thisCurve.p.x, thisCurve.p.y );
#if DEBUG_PATH_CREATION
	DDLogWarn(@"[%@] PATH: SMOOTH QUADRATIC CURVE to (%2.2f, %2.2f)..(%2.2f, %2.2f)", [SVGKPointsAndPathsParser class], thisCurve.c1.x, thisCurve.c1.y, thisCurve.p.x, thisCurve.p.y );
#endif

    return thisCurve;
}

/**
 curveto:
 ( "C" | "c" ) wsp* curveto-argument-sequence
 */
+ (SVGCurve) readCurvetoCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
{
#if VERBOSE_PARSE_SVG_COMMAND_STRINGS
	DDLogVerbose(@"Parsing command string: curve-to command");
#endif
    NSString* cmd;
    NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Cc"];

	if( ! [scanner scanCharactersFromSet:cmdFormat intoString:&cmd])
	{
		NSAssert( FALSE, @"failed to scan curve to command");
		return SVGCurveZero;
	}

    [SVGKPointsAndPathsParser readWhitespace:scanner];

    SVGCurve lastCurve = [SVGKPointsAndPathsParser readCurvetoArgumentSequence:scanner path:path relativeTo:origin isRelative:isRelative];
    return lastCurve;
}

/**
 curveto-argument-sequence:
 curveto-argument
 | curveto-argument comma-wsp? curveto-argument-sequence
 */
+ (SVGCurve) readCurvetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin isRelative:(BOOL) isRelative
{
	SVGCurve curve = [SVGKPointsAndPathsParser readCurvetoArgument:scanner path:path relativeTo:origin];

	while( ![scanner isAtEnd])
	{
		CGPoint newOrigin = isRelative ? curve.p : origin;

        curve = [SVGKPointsAndPathsParser readCurvetoArgument:scanner path:path relativeTo:newOrigin];
    }

    return curve;
}

/**
 curveto-argument:
 coordinate-pair comma-wsp? coordinate-pair comma-wsp? coordinate-pair
 */
+ (SVGCurve) readCurvetoArgument:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin
{
	SVGCurve curveResult;
    curveResult.c1 = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
	curveResult.c1.x += origin.x; // avoid allocating a new struct, an allocation here could happen MILLIONS of times in a large parse!
	curveResult.c1.y += origin.y;
    [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];

    curveResult.c2 = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
    curveResult.c2.x += origin.x; // avoid allocating a new struct, an allocation here could happen MILLIONS of times in a large parse!
	curveResult.c2.y += origin.y;
    [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];

    curveResult.p = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
    curveResult.p.x += origin.x; // avoid allocating a new struct, an allocation here could happen MILLIONS of times in a large parse!
	curveResult.p.y += origin.y;
    [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];

    CGPathAddCurveToPoint(path, NULL, curveResult.c1.x, curveResult.c1.y, curveResult.c2.x, curveResult.c2.y, curveResult.p.x, curveResult.p.y);
#if DEBUG_PATH_CREATION
	DDLogWarn(@"[%@] PATH: CURVE to (%2.2f, %2.2f)..(%2.2f, %2.2f)..(%2.2f, %2.2f)", [SVGKPointsAndPathsParser class], curveResult.c1.x, curveResult.c1.y, curveResult.c2.x, curveResult.c2.y, curveResult.p.x, curveResult.p.y);
#endif

    return curveResult;
}

/**
 smooth-curveto:
 ( "S" | "s" ) wsp* smooth-curveto-argument-sequence
 */
+ (SVGCurve) readSmoothCurvetoCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin withPrevCurve:(SVGCurve)prevCurve
{
    NSString* cmd;
    NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Ss"];
    BOOL ok = [scanner scanCharactersFromSet:cmdFormat intoString:&cmd];

    NSAssert(ok, @"failed to scan smooth curve to command");
    if (!ok) return SVGCurveZero;

    [SVGKPointsAndPathsParser readWhitespace:scanner];

    SVGCurve lastCurve = [SVGKPointsAndPathsParser readSmoothCurvetoArgumentSequence:scanner path:path relativeTo:origin withPrevCurve:prevCurve];
    return lastCurve;
}

/**
 smooth-curveto-argument-sequence:
 smooth-curveto-argument
 | smooth-curveto-argument comma-wsp? smooth-curveto-argument-sequence
 */
+ (SVGCurve) readSmoothCurvetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin withPrevCurve:(SVGCurve)prevCurve
{
    SVGCurve curve = [SVGKPointsAndPathsParser readSmoothCurvetoArgument:scanner path:path relativeTo:origin withPrevCurve:prevCurve];

    if (![scanner isAtEnd]) {
        curve = [SVGKPointsAndPathsParser readSmoothCurvetoArgumentSequence:scanner path:path relativeTo:curve.p withPrevCurve:curve];
    }

    return curve;
}

/**
 smooth-curveto-argument:
 coordinate-pair comma-wsp? coordinate-pair
 */
+ (SVGCurve) readSmoothCurvetoArgument:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin withPrevCurve:(SVGCurve)prevCurve
{
	// FIXME: reduce the allocations here; make one SVGCurve and update it, not multiple CGPoint's

    CGPoint p1 = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
    CGPoint coord1 = CGPointMake(p1.x+origin.x, p1.y+origin.y);
    [SVGKPointsAndPathsParser readCommaAndWhitespace:scanner];

    CGPoint p2 = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
    CGPoint coord2 = CGPointMake(p2.x+origin.x, p2.y+origin.y);

    SVGCurve thisCurve;
    if (SVGCurveEqualToCurve(SVGCurveZero, prevCurve)) {
        // assume control point is coincident with the current point
        thisCurve = SVGCurveMake(coord1.x, coord1.y, coord2.x, coord2.y, coord1.x, coord1.y);
    } else {
        // calculate the mirror of the previous control point
        CGPoint currentPoint = prevCurve.p;
        CGPoint controlPoint = prevCurve.c2;
        CGPoint mirrorCoord = CGPointMake(currentPoint.x+(currentPoint.x-controlPoint.x), currentPoint.y+(currentPoint.y-controlPoint.y));
        thisCurve = SVGCurveMake(mirrorCoord.x, mirrorCoord.y, coord1.x, coord1.y, coord2.x, coord2.y);
    }

    CGPathAddCurveToPoint(path, NULL, thisCurve.c1.x, thisCurve.c1.y, thisCurve.c2.x, thisCurve.c2.y, thisCurve.p.x, thisCurve.p.y);
#if DEBUG_PATH_CREATION
	DDLogWarn(@"[%@] PATH: SMOOTH CURVE to (%2.2f, %2.2f)..(%2.2f, %2.2f)..(%2.2f, %2.2f)", [SVGKPointsAndPathsParser class], thisCurve.c1.x, thisCurve.c1.y, thisCurve.c2.x, thisCurve.c2.y, thisCurve.p.x, thisCurve.p.y );
#endif

    return thisCurve;
}

/**
 vertical-lineto-argument-sequence:
 coordinate
 | coordinate comma-wsp? vertical-lineto-argument-sequence
 */
+ (CGPoint) readVerticalLinetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin
{
	// FIXME: reduce the allocations here; make one CGPoint and update it, not multiple
    CGFloat yValue;
	[SVGKPointsAndPathsParser readCoordinate:scanner intoFloat:&yValue];
    CGPoint vertCoord = CGPointMake(origin.x, origin.y+yValue);
    CGPoint currentPoint = CGPathGetCurrentPoint(path);
    CGPoint coord = CGPointMake(currentPoint.x, currentPoint.y+(vertCoord.y-currentPoint.y));
    CGPathAddLineToPoint(path, NULL, coord.x, coord.y);
#if DEBUG_PATH_CREATION
	DDLogWarn(@"[%@] PATH: VERTICAL LINE to (%2.2f, %2.2f)", [SVGKPointsAndPathsParser class], coord.x, coord.y );
#endif
    return coord;
}

/**
 vertical-lineto:
 ( "V" | "v" ) wsp* vertical-lineto-argument-sequence
 */
+ (CGPoint) readVerticalLinetoCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin
{
#if VERBOSE_PARSE_SVG_COMMAND_STRINGS
	DDLogVerbose(@"Parsing command string: vertical-line-to command");
#endif
    NSString* cmd;
    NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Vv"];
    BOOL ok = [scanner scanCharactersFromSet:cmdFormat intoString:&cmd];

    NSAssert(ok, @"failed to scan vertical line to command");
    if (!ok) return origin;

    [SVGKPointsAndPathsParser readWhitespace:scanner];

    CGPoint lastCoordinate = [SVGKPointsAndPathsParser readVerticalLinetoArgumentSequence:scanner path:path relativeTo:origin];
    return lastCoordinate;
}

/**
 horizontal-lineto-argument-sequence:
 coordinate
 | coordinate comma-wsp? horizontal-lineto-argument-sequence
 */
+ (CGPoint) readHorizontalLinetoArgumentSequence:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin
{
	// FIXME: reduce the allocations here; make one CGPoint and update it, not multiple

    CGFloat xValue;
	[SVGKPointsAndPathsParser readCoordinate:scanner intoFloat:&xValue];
    CGPoint horizCoord = CGPointMake(origin.x+xValue, origin.y);
    CGPoint currentPoint = CGPathGetCurrentPoint(path);
    CGPoint coord = CGPointMake(currentPoint.x+(horizCoord.x-currentPoint.x), currentPoint.y);
    CGPathAddLineToPoint(path, NULL, coord.x, coord.y);
#if DEBUG_PATH_CREATION
	DDLogWarn(@"[%@] PATH: HORIZONTAL LINE to (%2.2f, %2.2f)", [SVGKPointsAndPathsParser class], coord.x, coord.y );
#endif
    return coord;
}

/**
 horizontal-lineto:
 ( "H" | "h" ) wsp* horizontal-lineto-argument-sequence
 */
+ (CGPoint) readHorizontalLinetoCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin
{
#if VERBOSE_PARSE_SVG_COMMAND_STRINGS
	DDLogVerbose(@"Parsing command string: horizontal-line-to command");
#endif
    NSString* cmd;
    NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Hh"];

	if( ! [scanner scanCharactersFromSet:cmdFormat intoString:&cmd] )
	{
		NSAssert( FALSE, @"failed to scan horizontal line to command");
		return origin;
	}

    [SVGKPointsAndPathsParser readWhitespace:scanner];

    CGPoint lastCoordinate = [SVGKPointsAndPathsParser readHorizontalLinetoArgumentSequence:scanner path:path relativeTo:origin];
    return lastCoordinate;
}

+ (CGPoint) readCloseCommand:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin
{
#if VERBOSE_PARSE_SVG_COMMAND_STRINGS
	DDLogVerbose(@"Parsing command string: close command");
#endif
    NSString* cmd;
    NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Zz"];

	if( ! [scanner scanCharactersFromSet:cmdFormat intoString:&cmd] )
	{
		NSAssert( FALSE, @"failed to scan close command");
		return origin;
	}

    CGPathCloseSubpath(path);
#if DEBUG_PATH_CREATION
	DDLogWarn(@"[%@] PATH: finished path", [SVGKPointsAndPathsParser class] );
#endif

	return CGPathGetCurrentPoint(path);
}

+ (SVGCurve)readEllipticalArcArguments:(NSScanner*)scanner path:(CGMutablePathRef)path relativeTo:(CGPoint)origin
{
	NSCharacterSet* cmdFormat = [NSCharacterSet characterSetWithCharactersInString:@"Aa"];

	[scanner scanCharactersFromSet:cmdFormat intoString:nil];

	// need to find the center point of the ellipse from the two points and an angle
	// see http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes for these calculations

	CGPoint currentPt = CGPathGetCurrentPoint(path);

	CGFloat x1 = currentPt.x;
	CGFloat y1 = currentPt.y;

	CGPoint radii = [SVGKPointsAndPathsParser readCoordinatePair:scanner];
	CGFloat rx = fabs(radii.x);
	CGFloat ry = fabs(radii.y);

	CGFloat phi;

	[SVGKPointsAndPathsParser readCoordinate:scanner intoFloat:&phi];

	phi *= M_PI/180.;

	phi = fmod(phi, 2 * M_PI);

	CGPoint flags = [SVGKPointsAndPathsParser readCoordinatePair:scanner];

	BOOL largeArcFlag = flags.x != 0.;
	BOOL sweepFlag = flags.y != 0.;

	CGPoint endPoint = [SVGKPointsAndPathsParser readCoordinatePair:scanner];

	// end parsing

	CGFloat x2 = origin.x + endPoint.x;
	CGFloat y2 = origin.y + endPoint.y;

	SVGCurve curve;

	curve.p = CGPointMake(x2, y2);

	if (rx == 0 || ry == 0)
	{
		CGPathAddLineToPoint(path, NULL, curve.p.x, curve.p.y);
		return curve;
	}
	CGFloat cosPhi = cos(phi);
	CGFloat sinPhi = sin(phi);

	CGFloat	x1p = cosPhi * (x1-x2)/2. + sinPhi * (y1-y2)/2.;
	CGFloat	y1p = -sinPhi * (x1-x2)/2. + cosPhi * (y1-y2)/2.;

	CGFloat lhs;
	{
		CGFloat rx_2 = rx * rx;
		CGFloat ry_2 = ry * ry;
		CGFloat xp_2 = x1p * x1p;
		CGFloat yp_2 = y1p * y1p;

		CGFloat delta = xp_2/rx_2 + yp_2/ry_2;

		if (delta > 1.0)
		{
			rx *= sqrt(delta);
			ry *= sqrt(delta);
			rx_2 = rx * rx;
			ry_2 = ry * ry;
		}
		CGFloat sign = (largeArcFlag == sweepFlag) ? -1 : 1;
		CGFloat numerator = rx_2 * ry_2 - rx_2 * yp_2 - ry_2 * xp_2;
		CGFloat denom = rx_2 * yp_2 + ry_2 * xp_2;

		numerator = MAX(0, numerator);

		lhs = sign * sqrt(numerator/denom);
	}

	CGFloat cxp = lhs * (rx*y1p)/ry;
	CGFloat cyp = lhs * -((ry * x1p)/rx);

	CGFloat cx = cosPhi * cxp + -sinPhi * cyp + (x1+x2)/2.;
	CGFloat cy = cxp * sinPhi + cyp * cosPhi + (y1+y2)/2.;

	// transform our ellipse into the unit circle

	CGAffineTransform tr = CGAffineTransformMakeScale(1./rx, 1./ry);

	tr = CGAffineTransformRotate(tr, -phi);
	tr = CGAffineTransformTranslate(tr, -cx, -cy);

	CGPoint arcPt1 = CGPointApplyAffineTransform(CGPointMake(x1, y1), tr);
	CGPoint arcPt2 = CGPointApplyAffineTransform(CGPointMake(x2, y2), tr);

	CGFloat startAngle = atan2(arcPt1.y, arcPt1.x);
	CGFloat endAngle = atan2(arcPt2.y, arcPt2.x);

	CGFloat angleDelta = endAngle - startAngle;;

	if (sweepFlag)
	{
		if (angleDelta < 0)
			angleDelta += 2. * M_PI;
	}
	else
	{
		if (angleDelta > 0)
			angleDelta = angleDelta - 2 * M_PI;
	}
	// construct the inverse transform
	CGAffineTransform trInv = CGAffineTransformMakeTranslation( cx, cy);

	trInv = CGAffineTransformRotate(trInv, phi);
	trInv = CGAffineTransformScale(trInv, rx, ry);

	// add a inversely transformed circular arc to the current path
	CGPathAddRelativeArc( path, &trInv, 0, 0, 1., startAngle, angleDelta);

	return curve;
}

@end


text:
"use strict";

function SerialPort(path, options, openImmediately) {
	console.log("SerialPort constructed.");

	this.comName = path;

	if (options) {
		for (var key in this.options) {
			//console.log("Looking for " + key + " option.");
			if (options[key] != undefined) {
				//console.log("Replacing " + key + " with " + options[key]);
				this.options[key] = options[key];
			}
		}
	}

	if (typeof chrome != "undefined" && chrome.serial) {
		var self = this;

		if (openImmediately != false) {
			this.open();
		}

	} else {
		throw "No access to serial ports. Try loading as a Chrome Application.";
	}
}

SerialPort.prototype.options = {
    baudrate: 57600,
    buffersize: 1
};

SerialPort.prototype.connectionId = -1;

SerialPort.prototype.comName = "";

SerialPort.prototype.eventListeners = {};

SerialPort.prototype.open = function (callback) {
	console.log("Opening ", this.comName);
	chrome.serial.open(this.comName, {bitrate: this.options.baudrate}, this.proxy('onOpen', callback));
};

SerialPort.prototype.onOpen = function (callback, openInfo) {
	console.log("onOpen", callback, openInfo);
	this.connectionId = openInfo.connectionId;
	if (this.connectionId == -1) {
		this.publishEvent("error", "Could not open port.");
		return;
	}

	this.publishEvent("open", openInfo);

	console.log('Connected to port.', this.connectionId);

	typeof callback == "function" && callback(openInfo);

	chrome.serial.read(this.connectionId, this.options.buffersize, this.proxy('onRead'));

};

SerialPort.prototype.onRead = function (readInfo) {
	if (readInfo && readInfo.bytesRead > 0) {

		var uint8View = new Uint8Array(readInfo.data);
		var string = "";
		for (var i = 0; i < readInfo.bytesRead; i++) {
			string += String.fromCharCode(uint8View[i]);
		}

		//console.log("Got data", string, readInfo.data);

		//Maybe this should be a Buffer()
		this.publishEvent("data", uint8View);
		this.publishEvent("dataString", string);
	}

	chrome.serial.read(this.connectionId, this.options.buffersize, this.proxy('onRead'));
}

SerialPort.prototype.write = function (buffer, callback) {
	if (typeof callback != "function") { callback = function() {}; }

	//Make sure its not a browserify faux Buffer.
	if (buffer instanceof ArrayBuffer == false) {
		buffer = buffer2ArrayBuffer(buffer);
	}

	chrome.serial.write(this.connectionId, buffer, callback);
};

SerialPort.prototype.writeString = function (string, callback) {
	this.write(str2ab(string), callback);
};

SerialPort.prototype.close = function (callback) {
	chrome.serial.close(this.connectionId, this.proxy('onClose', callback));
};

SerialPort.prototype.onClose = function (callback) {
	this.connectionId = -1;
	console.log("Closed port", arguments);
	this.publishEvent("close");
	typeof callback == "function" && callback(openInfo);
};

SerialPort.prototype.flush = function (callback) {

};

//Expecting: data, error
SerialPort.prototype.on = function (eventName, callback) {
	if (this.eventListeners[eventName] == undefined) {
		this.eventListeners[eventName] = [];
	}
	if (typeof callback == "function") {
		this.eventListeners[eventName].push(callback);
	} else {
		throw "can not subscribe with a non function callback";
	}
}

SerialPort.prototype.publishEvent = function (eventName, data) {
	if (this.eventListeners[eventName] != undefined) {
		for (var i = 0; i < this.eventListeners[eventName].length; i++) {
			this.eventListeners[eventName][i](data);
		}
	}
}

SerialPort.prototype.proxy = function () {
	var self = this;
	var proxyArgs = [];

	//arguments isnt actually an array.
	for (var i = 0; i < arguments.length; i++) {
	    proxyArgs[i] = arguments[i];
	}

	var functionName = proxyArgs.splice(0, 1)[0];

	var func = function() {
		var funcArgs = [];
		for (var i = 0; i < arguments.length; i++) {
		    funcArgs[i] = arguments[i];
		}
		var allArgs = proxyArgs.concat(funcArgs);

		self[functionName].apply(self, allArgs);
	}

	return func;
}

function SerialPortList(callback) {
	if (typeof chrome != "undefined" && chrome.serial) {
		chrome.serial.getPorts(function(ports) {
			var portObjects = Array(ports.length);
			for (var i = 0; i < ports.length; i++) {
				portObjects[i] = new SerialPort(ports[i], null, false);
			}
			callback(null, portObjects);
		});
	} else {
		callback("No access to serial ports. Try loading as a Chrome Application.", null);
	}
};

// Convert string to ArrayBuffer
function str2ab(str) {
	var buf = new ArrayBuffer(str.length);
	var bufView = new Uint8Array(buf);
	for (var i = 0; i < str.length; i++) {
		bufView[i] = str.charCodeAt(i);
	}
	return buf;
}

// Convert buffer to ArrayBuffer
function buffer2ArrayBuffer(buffer) {
	var buf = new ArrayBuffer(buffer.length);
	var bufView = new Uint8Array(buf);
	for (var i = 0; i < buffer.length; i++) {
		bufView[i] = buffer[i];
	}
	return buf;
}

module.exports = {
	SerialPort: SerialPort,
	list: SerialPortList,
	used: [] //TODO: Populate this somewhere.
};