001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.commons.compress.changes;
020
021 import java.io.InputStream;
022 import java.util.Iterator;
023 import java.util.LinkedHashSet;
024 import java.util.Set;
025
026 import org.apache.commons.compress.archivers.ArchiveEntry;
027
028 /**
029 * ChangeSet collects and performs changes to an archive.
030 * Putting delete changes in this ChangeSet from multiple threads can
031 * cause conflicts.
032 *
033 * @NotThreadSafe
034 */
035 public final class ChangeSet {
036
037 private final Set<Change> changes = new LinkedHashSet<Change>();
038
039 /**
040 * Deletes the file with the filename from the archive.
041 *
042 * @param filename
043 * the filename of the file to delete
044 */
045 public void delete(final String filename) {
046 addDeletion(new Change(filename, Change.TYPE_DELETE));
047 }
048
049 /**
050 * Deletes the directory tree from the archive.
051 *
052 * @param dirName
053 * the name of the directory tree to delete
054 */
055 public void deleteDir(final String dirName) {
056 addDeletion(new Change(dirName, Change.TYPE_DELETE_DIR));
057 }
058
059 /**
060 * Adds a new archive entry to the archive.
061 *
062 * @param pEntry
063 * the entry to add
064 * @param pInput
065 * the datastream to add
066 */
067 public void add(final ArchiveEntry pEntry, final InputStream pInput) {
068 this.add(pEntry, pInput, true);
069 }
070
071 /**
072 * Adds a new archive entry to the archive.
073 * If replace is set to true, this change will replace all other additions
074 * done in this ChangeSet and all existing entries in the original stream.
075 *
076 * @param pEntry
077 * the entry to add
078 * @param pInput
079 * the datastream to add
080 * @param replace
081 * indicates the this change should replace existing entries
082 */
083 public void add(final ArchiveEntry pEntry, final InputStream pInput, final boolean replace) {
084 addAddition(new Change(pEntry, pInput, replace));
085 }
086
087 /**
088 * Adds an addition change.
089 *
090 * @param pChange
091 * the change which should result in an addition
092 */
093 private void addAddition(Change pChange) {
094 if (Change.TYPE_ADD != pChange.type() ||
095 pChange.getInput() == null) {
096 return;
097 }
098
099 if (!changes.isEmpty()) {
100 for (Iterator<Change> it = changes.iterator(); it.hasNext();) {
101 Change change = it.next();
102 if (change.type() == Change.TYPE_ADD
103 && change.getEntry() != null) {
104 ArchiveEntry entry = change.getEntry();
105
106 if(entry.equals(pChange.getEntry())) {
107 if(pChange.isReplaceMode()) {
108 it.remove();
109 changes.add(pChange);
110 return;
111 } else {
112 // do not add this change
113 return;
114 }
115 }
116 }
117 }
118 }
119 changes.add(pChange);
120 }
121
122 /**
123 * Adds an delete change.
124 *
125 * @param pChange
126 * the change which should result in a deletion
127 */
128 private void addDeletion(Change pChange) {
129 if ((Change.TYPE_DELETE != pChange.type() &&
130 Change.TYPE_DELETE_DIR != pChange.type()) ||
131 pChange.targetFile() == null) {
132 return;
133 }
134 String source = pChange.targetFile();
135
136 if (!changes.isEmpty()) {
137 for (Iterator<Change> it = changes.iterator(); it.hasNext();) {
138 Change change = it.next();
139 if (change.type() == Change.TYPE_ADD
140 && change.getEntry() != null) {
141 String target = change.getEntry().getName();
142
143 if (Change.TYPE_DELETE == pChange.type() && source.equals(target)) {
144 it.remove();
145 } else if (Change.TYPE_DELETE_DIR == pChange.type() &&
146 target.matches(source + "/.*")) {
147 it.remove();
148 }
149 }
150 }
151 }
152 changes.add(pChange);
153 }
154
155 /**
156 * Returns the list of changes as a copy. Changes on this set
157 * are not reflected on this ChangeSet and vice versa.
158 * @return the changes as a copy
159 */
160 Set<Change> getChanges() {
161 return new LinkedHashSet<Change>(changes);
162 }
163 }