diff --git a/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/ArchiveFormats.java b/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/ArchiveFormats.java index 65466a994..1be126aa8 100644 --- a/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/ArchiveFormats.java +++ b/org.eclipse.jgit.archive/src/org/eclipse/jgit/archive/ArchiveFormats.java @@ -66,7 +66,6 @@ private static final void register(String name, ArchiveCommand.Format fmt) { * Register all included archive formats so they can be used * as arguments to the ArchiveCommand.setFormat() method. * - * Should not be called twice without a call to stop() in between. * Not thread-safe. */ public static void registerAll() { diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index 3bc682be7..bb95fa85f 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -12,7 +12,7 @@ anExceptionOccurredWhileTryingToAddTheIdOfHEAD=An exception occurred while tryin anSSHSessionHasBeenAlreadyCreated=An SSH session has been already created applyingCommit=Applying {0} archiveFormatAlreadyAbsent=Archive format already absent: {0} -archiveFormatAlreadyRegistered=Archive format already registered: {0} +archiveFormatAlreadyRegistered=Archive format already registered with different implementation: {0} argumentIsNotAValidCommentString=Invalid comment: {0} atLeastOnePathIsRequired=At least one path is required. atLeastOnePatternIsRequired=At least one pattern is required. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java index 1bafb5efc..70ab73015 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java @@ -189,65 +189,135 @@ public String getFormat() { } } + private static class FormatEntry { + final Format format; + /** Number of times this format has been registered. */ + final int refcnt; + + public FormatEntry(Format format, int refcnt) { + if (format == null) + throw new NullPointerException(); + this.format = format; + this.refcnt = refcnt; + } + }; + /** * Available archival formats (corresponding to values for * the --format= option) */ - private static final ConcurrentMap> formats = - new ConcurrentHashMap>(); + private static final ConcurrentMap formats = + new ConcurrentHashMap(); + + /** + * Replaces the entry for a key only if currently mapped to a given + * value. + * + * @param map a map + * @param key key with which the specified value is associated + * @param oldValue expected value for the key (null if should be absent). + * @param newValue value to be associated with the key (null to remove). + * @return true if the value was replaced + */ + private static boolean replace(ConcurrentMap map, + K key, V oldValue, V newValue) { + if (oldValue == null && newValue == null) // Nothing to do. + return true; + + if (oldValue == null) + return map.putIfAbsent(key, newValue) == null; + else if (newValue == null) + return map.remove(key, oldValue); + else + return map.replace(key, oldValue, newValue); + } /** * Adds support for an additional archival format. To avoid * unnecessary dependencies, ArchiveCommand does not have support * for any formats built in; use this function to add them. - * + *

* OSGi plugins providing formats should call this function at * bundle activation time. + *

+ * It is okay to register the same archive format with the same + * name multiple times, but don't forget to unregister it that + * same number of times, too. + *

+ * Registering multiple formats with different names and the + * same or overlapping suffixes results in undefined behavior. + * TODO: check that suffixes don't overlap. * * @param name name of a format (e.g., "tar" or "zip"). * @param fmt archiver for that format * @throws JGitInternalException - * An archival format with that name was already registered. + * A different archival format with that name was + * already registered. */ public static void registerFormat(String name, Format fmt) { - // TODO(jrn): Check that suffixes don't overlap. + if (fmt == null) + throw new NullPointerException(); - if (formats.putIfAbsent(name, fmt) != null) - throw new JGitInternalException(MessageFormat.format( - JGitText.get().archiveFormatAlreadyRegistered, - name)); + FormatEntry old, entry; + do { + old = formats.get(name); + if (old == null) { + entry = new FormatEntry(fmt, 1); + continue; + } + if (!old.format.equals(fmt)) + throw new JGitInternalException(MessageFormat.format( + JGitText.get().archiveFormatAlreadyRegistered, + name)); + entry = new FormatEntry(old.format, old.refcnt + 1); + } while (!replace(formats, name, old, entry)); } /** - * Removes support for an archival format so its Format can be - * garbage collected. + * Marks support for an archival format as no longer needed so its + * Format can be garbage collected if no one else is using it either. + *

+ * In other words, this decrements the reference count for an + * archival format. If the reference count becomes zero, removes + * support for that format. * * @param name name of format (e.g., "tar" or "zip"). * @throws JGitInternalException * No such archival format was registered. */ public static void unregisterFormat(String name) { - if (formats.remove(name) == null) - throw new JGitInternalException(MessageFormat.format( - JGitText.get().archiveFormatAlreadyAbsent, - name)); + FormatEntry old, entry; + do { + old = formats.get(name); + if (old == null) + throw new JGitInternalException(MessageFormat.format( + JGitText.get().archiveFormatAlreadyAbsent, + name)); + if (old.refcnt == 1) { + entry = null; + continue; + } + entry = new FormatEntry(old.format, old.refcnt - 1); + } while (!replace(formats, name, old, entry)); } private static Format formatBySuffix(String filenameSuffix) throws UnsupportedFormatException { if (filenameSuffix != null) - for (Format fmt : formats.values()) + for (FormatEntry entry : formats.values()) { + Format fmt = entry.format; for (String sfx : fmt.suffixes()) if (filenameSuffix.endsWith(sfx)) return fmt; + } return lookupFormat("tar"); //$NON-NLS-1$ } private static Format lookupFormat(String formatName) throws UnsupportedFormatException { - Format fmt = formats.get(formatName); - if (fmt == null) + FormatEntry entry = formats.get(formatName); + if (entry == null) throw new UnsupportedFormatException(formatName); - return fmt; + return entry.format; } private OutputStream out;