View Javadoc
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   *   https://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  
21  import java.io.InputStream;
22  import java.util.Iterator;
23  import java.util.LinkedHashSet;
24  import java.util.Set;
25  import java.util.regex.Pattern;
26  
27  import org.apache.commons.compress.archivers.ArchiveEntry;
28  import org.apache.commons.compress.changes.Change.ChangeType;
29  
30  /**
31   * ChangeSet collects and performs changes to an archive. Putting delete changes in this ChangeSet from multiple threads can cause conflicts.
32   *
33   * @param <E> The ArchiveEntry type.
34   * @NotThreadSafe
35   */
36  public final class ChangeSet<E extends ArchiveEntry> {
37  
38      private final Set<Change<E>> changes = new LinkedHashSet<>();
39  
40      /**
41       * Constructs a new instance.
42       */
43      public ChangeSet() {
44          // empty
45      }
46  
47      /**
48       * Adds a new archive entry to the archive.
49       *
50       * @param entry the entry to add
51       * @param input the data stream to add
52       */
53      public void add(final E entry, final InputStream input) {
54          this.add(entry, input, true);
55      }
56  
57      /**
58       * 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
59       * entries in the original stream.
60       *
61       * @param entry   the entry to add
62       * @param input   the data stream to add
63       * @param replace indicates the this change should replace existing entries
64       */
65      public void add(final E entry, final InputStream input, final boolean replace) {
66          addAddition(new Change<>(entry, input, replace));
67      }
68  
69      /**
70       * Adds an addition change.
71       *
72       * @param addChange the change which should result in an addition
73       */
74      @SuppressWarnings("resource") // InputStream is NOT allocated
75      private void addAddition(final Change<E> addChange) {
76          if (Change.ChangeType.ADD != addChange.getType() || addChange.getInputStream() == null) {
77              return;
78          }
79  
80          if (!changes.isEmpty()) {
81              for (final Iterator<Change<E>> it = changes.iterator(); it.hasNext();) {
82                  final Change<E> change = it.next();
83                  if (change.getType() == Change.ChangeType.ADD && change.getEntry() != null) {
84                      final ArchiveEntry entry = change.getEntry();
85  
86                      if (entry.equals(addChange.getEntry())) {
87                          if (addChange.isReplaceMode()) {
88                              it.remove();
89                              changes.add(addChange);
90                          }
91                          // do not add this change
92                          return;
93                      }
94                  }
95              }
96          }
97          changes.add(addChange);
98      }
99  
100     /**
101      * Adds an delete change.
102      *
103      * @param deleteChange the change which should result in a deletion
104      */
105     private void addDeletion(final Change<E> deleteChange) {
106         if (ChangeType.DELETE != deleteChange.getType() && ChangeType.DELETE_DIR != deleteChange.getType() || deleteChange.getTargetFileName() == null) {
107             return;
108         }
109         final String source = deleteChange.getTargetFileName();
110         final Pattern pattern = Pattern.compile(source + "/.*");
111         if (source != null && !changes.isEmpty()) {
112             for (final Iterator<Change<E>> it = changes.iterator(); it.hasNext();) {
113                 final Change<E> change = it.next();
114                 if (change.getType() == ChangeType.ADD && change.getEntry() != null) {
115                     final String target = change.getEntry().getName();
116                     if (target == null) {
117                         continue;
118                     }
119                     if (ChangeType.DELETE == deleteChange.getType() && source.equals(target)
120                             || ChangeType.DELETE_DIR == deleteChange.getType() && pattern.matcher(target).matches()) {
121                         it.remove();
122                     }
123                 }
124             }
125         }
126         changes.add(deleteChange);
127     }
128 
129     /**
130      * Deletes the file with the file name from the archive.
131      *
132      * @param fileName the file name of the file to delete
133      */
134     public void delete(final String fileName) {
135         addDeletion(new Change<>(fileName, ChangeType.DELETE));
136     }
137 
138     /**
139      * Deletes the directory tree from the archive.
140      *
141      * @param dirName the name of the directory tree to delete
142      */
143     public void deleteDir(final String dirName) {
144         addDeletion(new Change<>(dirName, ChangeType.DELETE_DIR));
145     }
146 
147     /**
148      * Gets the list of changes as a copy. Changes on this set are not reflected on this ChangeSet and vice versa.
149      *
150      * @return the changes as a copy
151      */
152     Set<Change<E>> getChanges() {
153         return new LinkedHashSet<>(changes);
154     }
155 }