ChangeSet.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one
  3.  * or more contributor license agreements.  See the NOTICE file
  4.  * distributed with this work for additional information
  5.  * regarding copyright ownership.  The ASF licenses this file
  6.  * to you under the Apache License, Version 2.0 (the
  7.  * "License"); you may not use this file except in compliance
  8.  * with the License.  You may obtain a copy of the License at
  9.  *
  10.  * http://www.apache.org/licenses/LICENSE-2.0
  11.  *
  12.  * Unless required by applicable law or agreed to in writing,
  13.  * software distributed under the License is distributed on an
  14.  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15.  * KIND, either express or implied.  See the License for the
  16.  * specific language governing permissions and limitations
  17.  * under the License.
  18.  */
  19. package org.apache.commons.compress.changes;

  20. import java.io.InputStream;
  21. import java.util.Iterator;
  22. import java.util.LinkedHashSet;
  23. import java.util.Set;
  24. import java.util.regex.Pattern;

  25. import org.apache.commons.compress.archivers.ArchiveEntry;
  26. import org.apache.commons.compress.changes.Change.ChangeType;

  27. /**
  28.  * ChangeSet collects and performs changes to an archive. Putting delete changes in this ChangeSet from multiple threads can cause conflicts.
  29.  *
  30.  * @param <E> The ArchiveEntry type.
  31.  * @NotThreadSafe
  32.  */
  33. public final class ChangeSet<E extends ArchiveEntry> {

  34.     private final Set<Change<E>> changes = new LinkedHashSet<>();

  35.     /**
  36.      * Adds a new archive entry to the archive.
  37.      *
  38.      * @param entry the entry to add
  39.      * @param input the data stream to add
  40.      */
  41.     public void add(final E entry, final InputStream input) {
  42.         this.add(entry, input, true);
  43.     }

  44.     /**
  45.      * Adds a new archive entry to the archive. If replace is set to true, this change will replace all other additions done in this ChangeSet and all existing
  46.      * entries in the original stream.
  47.      *
  48.      * @param entry   the entry to add
  49.      * @param input   the data stream to add
  50.      * @param replace indicates the this change should replace existing entries
  51.      */
  52.     public void add(final E entry, final InputStream input, final boolean replace) {
  53.         addAddition(new Change<>(entry, input, replace));
  54.     }

  55.     /**
  56.      * Adds an addition change.
  57.      *
  58.      * @param addChange the change which should result in an addition
  59.      */
  60.     @SuppressWarnings("resource") // InputStream is NOT allocated
  61.     private void addAddition(final Change<E> addChange) {
  62.         if (Change.ChangeType.ADD != addChange.getType() || addChange.getInputStream() == null) {
  63.             return;
  64.         }

  65.         if (!changes.isEmpty()) {
  66.             for (final Iterator<Change<E>> it = changes.iterator(); it.hasNext();) {
  67.                 final Change<E> change = it.next();
  68.                 if (change.getType() == Change.ChangeType.ADD && change.getEntry() != null) {
  69.                     final ArchiveEntry entry = change.getEntry();

  70.                     if (entry.equals(addChange.getEntry())) {
  71.                         if (addChange.isReplaceMode()) {
  72.                             it.remove();
  73.                             changes.add(addChange);
  74.                         }
  75.                         // do not add this change
  76.                         return;
  77.                     }
  78.                 }
  79.             }
  80.         }
  81.         changes.add(addChange);
  82.     }

  83.     /**
  84.      * Adds an delete change.
  85.      *
  86.      * @param deleteChange the change which should result in a deletion
  87.      */
  88.     private void addDeletion(final Change<E> deleteChange) {
  89.         if (ChangeType.DELETE != deleteChange.getType() && ChangeType.DELETE_DIR != deleteChange.getType() || deleteChange.getTargetFileName() == null) {
  90.             return;
  91.         }
  92.         final String source = deleteChange.getTargetFileName();
  93.         final Pattern pattern = Pattern.compile(source + "/.*");
  94.         if (source != null && !changes.isEmpty()) {
  95.             for (final Iterator<Change<E>> it = changes.iterator(); it.hasNext();) {
  96.                 final Change<E> change = it.next();
  97.                 if (change.getType() == ChangeType.ADD && change.getEntry() != null) {
  98.                     final String target = change.getEntry().getName();
  99.                     if (target == null) {
  100.                         continue;
  101.                     }
  102.                     if (ChangeType.DELETE == deleteChange.getType() && source.equals(target)
  103.                             || ChangeType.DELETE_DIR == deleteChange.getType() && pattern.matcher(target).matches()) {
  104.                         it.remove();
  105.                     }
  106.                 }
  107.             }
  108.         }
  109.         changes.add(deleteChange);
  110.     }

  111.     /**
  112.      * Deletes the file with the file name from the archive.
  113.      *
  114.      * @param fileName the file name of the file to delete
  115.      */
  116.     public void delete(final String fileName) {
  117.         addDeletion(new Change<>(fileName, ChangeType.DELETE));
  118.     }

  119.     /**
  120.      * Deletes the directory tree from the archive.
  121.      *
  122.      * @param dirName the name of the directory tree to delete
  123.      */
  124.     public void deleteDir(final String dirName) {
  125.         addDeletion(new Change<>(dirName, ChangeType.DELETE_DIR));
  126.     }

  127.     /**
  128.      * Gets the list of changes as a copy. Changes on this set are not reflected on this ChangeSet and vice versa.
  129.      *
  130.      * @return the changes as a copy
  131.      */
  132.     Set<Change<E>> getChanges() {
  133.         return new LinkedHashSet<>(changes);
  134.     }
  135. }