/** * */ package com.perforce.p4java.impl.generic.core.file; import java.text.FieldPosition; import java.text.SimpleDateFormat; import java.util.Date; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.perforce.p4java.Log; import com.perforce.p4java.core.IChangelist; import com.perforce.p4java.core.file.IFileSpec; /** * Conveniently bundles up possible Perforce path-based metadata * annotations and associated operations. This includes revision, * label, changelist, and date annotations (at least). Most useful * for converting to / from string representations associated with * string file paths. */ public class PathAnnotations { /** * The string used to start Perforce file revision metadata annotations. */ public static final String REV_PFX = "#"; /** * The string used to separate Perforce file revision metadata annotations. */ public static final String REV_SEP = ",#"; /** * The string used to start Perforce file label / changelist / date metadata * annotations. */ public static final String NONREV_PFX = "@"; // non-revision prefix, i.e. // label, date and changelist prefix... protected int startRevision = IFileSpec.NO_FILE_REVISION; protected int endRevision = IFileSpec.NO_FILE_REVISION; protected Date date = null; protected int changelistId = IChangelist.UNKNOWN; protected String label = null; /** * Describes what to search for to see if any Perforce revision annotation metadata * is in the path string. */ private static Pattern revMetadata = Pattern.compile( "((#)(\\d+)$)" + "|(#none$)" + "|(#head$)" + "|(#have$)" + "|(#have,#head$)" + "|((#)(\\d+),#(\\d+)$)" + "|((#)(\\d+),#head$)" + "|((#)(\\d+),#have$)" + "|(#)have,(\\d+)$" ); /** * Describes what to search for to see if any Perforce non-revision annotation metadata * is in the path string. */ private static Pattern nonRevMetadata = Pattern.compile( "((@)(\\d+)$)" + "|((@)(\\d+\\/\\d+\\/\\d+)$)" + "|((@)(\\d+\\/\\d+\\/\\d+:\\d+:\\d+:\\d+)$)" + "|((@)([^@,%#]+)$)" ); private static Pattern revNumNum = Pattern.compile("(#)(\\d+),#(\\d+)$"); // #2,#3 private static Pattern revNumHead = Pattern.compile("(#)(\\d+),#head$"); // #3,#head private static Pattern revNumHave = Pattern.compile("(#)(\\d+),#have$"); // #3,#have private static Pattern revHaveNum = Pattern.compile("#have,#(\\d+)$"); // #have,#3 private static Pattern revNum = Pattern.compile("(#)(\\d+)$"); // #3 private static Pattern revNone = Pattern.compile("#none$"); // #none private static Pattern revHead = Pattern.compile("#head$"); // #head private static Pattern revHave = Pattern.compile("#have$"); // #have private static Pattern revHaveHead = Pattern.compile("#have,#head$"); // #have,#head private static Pattern nonrevChange = Pattern.compile("(@)(\\d+)$"); // @12345 private static Pattern nonrevDateShort = Pattern.compile("(@)(\\d+\\/\\d+\\/\\d+)$"); // @yyyy/MM/dd private static Pattern nonrevDateFull = Pattern.compile("(@)(\\d+\\/\\d+\\/\\d+:\\d+:\\d+:\\d+)$"); // @yyyy/MM/dd:HH:mm:ss private static Pattern nonrevLabel = Pattern.compile("(@)([^@,%#]+)$"); // @labelname /** * Construct a PathAnnotations object with default field values:<br> * startRev, endRev = IFileSpec.NO_FILE_REVISION;<br> * date, label = null;<br> * changelist = IChangelist.UNKNOWN. */ public PathAnnotations() { } /** * Construct a PathAnnotations object from explicit candidate field values. */ public PathAnnotations(int startRevision, int endRevision, Date date, int changelistId, String label) { this.startRevision = startRevision; this.endRevision = endRevision; this.date = date; this.changelistId = changelistId; this.label = label; } /** * Construct a PathAnnotations object for a specific file spec. If fileSpec * is null, this is equivalent to calling the default constructor. */ public PathAnnotations(IFileSpec fileSpec) { if (fileSpec != null) { this.startRevision = fileSpec.getStartRevision(); this.endRevision = fileSpec.getEndRevision(); this.date = fileSpec.getDate(); this.changelistId = fileSpec.getChangelistId(); this.label = fileSpec.getLabel(); } } /** * Construct a PathAnnotations object from the passed-in string, * which is assumed to contain a path string with optional * Perforce annotations. If pathStr is null, or contains no * parseable annotations, this is equivalent to calling the * default constructor. */ public PathAnnotations(String pathStr) { if (pathStr != null) { if (hasRevisionAnnotations(pathStr) >= 0) { extractRevisionData(pathStr, this); } else if (hasNonRevisionAnnotations(pathStr) >= 0) { extractNonRevisionData(pathStr, this); } } } /** * Strip any Perforce file metadata annotations from the passed-in string. * Returns the string as-is if there were no annotations or if the string * was null. Will return the empty (not null) string if there was no actual * path part of the string and pathStr wasn't null. */ public static String stripAnnotations(String pathStr) { if (pathStr != null) { int i = -1; if ((i = hasRevisionAnnotations(pathStr)) >= 0) { return pathStr.substring(0, i); } else if ((i = hasNonRevisionAnnotations(pathStr)) >= 0) { return pathStr.substring(0, i); } } return pathStr; } /** * Get all Perforce metadata annotations from the passed-in string. If * there are no annotations, or the passed-in pathStr argument is null, * returns null. */ public static PathAnnotations getAnnotations(String pathStr) { if ((pathStr != null) && (PathAnnotations.hasPerforceAnnotations(pathStr))) { return new PathAnnotations(pathStr); } return null; } /** * Return non-negative index of first non-revision Perforce annotations match in the * string if the passed-in string is not null has Perforce * revision annotations appended to it. Otherwise returns -1. */ public static int hasRevisionAnnotations(String pathStr) { if (pathStr != null) { if (pathStr.contains(REV_PFX)) { Matcher mat = revMetadata.matcher(pathStr); if (mat.find()) { return mat.start(); } } } return -1; } /** * Return non-negative index of first non-revision Perforce annotations match in the * string if the passed-in string is not null and has label, changelist, date, * etc., specs appended. Otherwise returns -1. */ public static int hasNonRevisionAnnotations(String pathStr) { if (pathStr != null) { if (pathStr.contains(NONREV_PFX)) { Matcher mat = nonRevMetadata.matcher(pathStr); if (mat.find()) { return mat.start(); } } } return -1; } /** * Return true if the passed-in path is not null and has Perforce file * metadata appended to it. */ public static boolean hasPerforceAnnotations(String pathStr) { return (hasRevisionAnnotations(pathStr) >= 0) || (hasNonRevisionAnnotations(pathStr) >= 0); } /** * Extract any start / end revision info from the passed-in pathStr and put it into * the passed-in pathAnnotations object. Returns true if it found any parseable * revision information, false otherwise.<p> * * If either or both pathStr and pathAnnotations is null, returns false. */ public static boolean extractRevisionData(String pathStr, PathAnnotations pathAnnotations) { /* * #n * #head * #n,#m * #n,#head * #none * #have * #have,#head (is this legal?) */ try { // Note: match order is significant below; don't change it unless // you're sure you know what you're doing... if ((pathStr != null) && (pathAnnotations != null)) { Matcher mat = revNumNum.matcher(pathStr); if (mat.find()) { pathAnnotations.startRevision = new Integer(mat.group(2)); pathAnnotations.endRevision = new Integer(mat.group(3)); return true; } mat = revNumHead.matcher(pathStr); if (mat.find()) { pathAnnotations.startRevision = new Integer(mat.group(2)); pathAnnotations.endRevision = IFileSpec.HEAD_REVISION; return true; } mat = revNumHave.matcher(pathStr); if (mat.find()) { pathAnnotations.startRevision = new Integer(mat.group(2)); pathAnnotations.endRevision = IFileSpec.HAVE_REVISION; return true; } mat = revHaveNum.matcher(pathStr); if (mat.find()) { pathAnnotations.startRevision = IFileSpec.HAVE_REVISION; pathAnnotations.endRevision = new Integer(mat.group(1)); return true; } mat = revHaveHead.matcher(pathStr); if (mat.find()) { pathAnnotations.startRevision = IFileSpec.HAVE_REVISION; pathAnnotations.endRevision = IFileSpec.HEAD_REVISION; return true; } mat = revNum.matcher(pathStr); if (mat.find()) { pathAnnotations.startRevision = IFileSpec.NO_FILE_REVISION; pathAnnotations.endRevision = new Integer(mat.group(2)); return true; } mat = revHead.matcher(pathStr); if (mat.find()) { pathAnnotations.startRevision = IFileSpec.NO_FILE_REVISION; pathAnnotations.endRevision = IFileSpec.HEAD_REVISION; return true; } mat = revHave.matcher(pathStr); if (mat.find()) { pathAnnotations.startRevision = IFileSpec.NO_FILE_REVISION; pathAnnotations.endRevision = IFileSpec.HAVE_REVISION; return true; } mat = revNone.matcher(pathStr); if (mat.find()) { pathAnnotations.startRevision = IFileSpec.NO_FILE_REVISION; pathAnnotations.endRevision = IFileSpec.NONE_REVISION; return true; } } } catch (Throwable thr) { Log.error("Unexpected exception in PathAnnotations.extractRevisionData; " + "pathStr: " + pathStr + "; message: " + thr.getLocalizedMessage()); Log.exception(thr); } return false; } /** * Extract any non-revision info from the passed-in pathStr and put it into * the passed-in pathAnnotations object. Returns true if it found any parseable * label / changelist / date (etc.) information, false otherwise.<p> * * Note that the candidate string "@2009/09/12" is ambiguous -- it could be * either a date or a label by a generous reading of the Perforce specs -- but * we take the "if it looks like a duck..." approach here and parse it as a date * if at all possible. Similarly for the string "@12345" which could be a changelist * ID or a label ID -- we parse it as a changelist ID if at all possible.<p> * * If either or both pathStr and pathAnnotations is null, returns false. */ public static boolean extractNonRevisionData(String pathStr, PathAnnotations pathAnnotations) { // @date: @2009/08/15 // @changelistid: @34567 // @label: @Label10A // NOTE: the order of matching below is critical; don't change it unless // you know what you're doing and why you're doing it... if ((pathStr != null) && (pathAnnotations != null)) { try { Matcher mat = nonrevChange.matcher(pathStr); if (mat.find()) { pathAnnotations.changelistId = new Integer(mat.group(2)); return true; } mat = nonrevDateShort.matcher(pathStr); if (mat.find()) { pathAnnotations.date = new SimpleDateFormat("yyyy/MM/dd").parse(mat.group(2));// HH:mm:ss return true; } mat = nonrevDateFull.matcher(pathStr); if (mat.find()) { pathAnnotations.date = new SimpleDateFormat("yyyy/MM/dd:HH:mm:ss").parse(mat.group(2));// HH:mm:ss return true; } mat = nonrevLabel.matcher(pathStr); if (mat.find()) { pathAnnotations.label = mat.group(2); return true; } } catch (Exception exc) { Log.error("Unexpected parse exception in PathAnnotations.extractNonRevData: " + exc.getLocalizedMessage()); Log.exception(exc); } } return false; } /** * Return a Perforce-standard string representation of this * annotation. Will return an empty (not null) string if there's * nothing to represent. */ public String toString() { if ((startRevision != IFileSpec.NO_FILE_REVISION) && (endRevision != IFileSpec.NO_FILE_REVISION)) { return REV_PFX + revString(startRevision) + REV_SEP + revString(endRevision); } else if (endRevision != IFileSpec.NO_FILE_REVISION) { return REV_PFX + revString(endRevision); } else if (getChangelistId() != IChangelist.UNKNOWN) { return NONREV_PFX + getChangelistId(); } else if (getLabel() != null) { return NONREV_PFX + getLabel(); } else if (getDate() != null) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd:HH:mm:ss"); StringBuffer sBuf = new StringBuffer(); sdf.format(getDate(), sBuf, new FieldPosition(0)); return NONREV_PFX + sBuf.toString(); } return ""; } public int getStartRevision() { return startRevision; } public void setStartRevision(int startRev) { this.startRevision = startRev; } public int getEndRevision() { return endRevision; } public void setEndRevision(int endRev) { this.endRevision = endRev; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } public int getChangelistId() { return changelistId; } public void setChangelistId(int changelistId) { this.changelistId = changelistId; } public String getLabel() { return label; } public void setLabel(String label) { this.label = label; } private static String revString(int revision) { switch (revision) { case IFileSpec.NO_FILE_REVISION: return IFileSpec.NO_REVISION_STRING; case IFileSpec.NONE_REVISION: return IFileSpec.NONE_REVISION_STRING; case IFileSpec.HAVE_REVISION: return IFileSpec.HAVE_REVISION_STRING; case IFileSpec.HEAD_REVISION: return IFileSpec.HEAD_REVISION_STRING; default: return "" + revision; } } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 19903 | stuartrowe |
Branching //guest/perforce_software/p4java/... to //guest/stuartrowe/p4java/... |
||
//guest/perforce_software/p4java/r14.1/src/main/java/com/perforce/p4java/impl/generic/core/file/PathAnnotations.java | |||||
#1 | 12541 | Matt Attaway | Initial add of the 14.1 p4java source code |