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.IOException; 022 import java.io.InputStream; 023 import java.util.Iterator; 024 import java.util.LinkedHashSet; 025 import java.util.Set; 026 027 import org.apache.commons.compress.archivers.ArchiveEntry; 028 import org.apache.commons.compress.archivers.ArchiveInputStream; 029 import org.apache.commons.compress.archivers.ArchiveOutputStream; 030 import org.apache.commons.compress.utils.IOUtils; 031 032 /** 033 * Performs ChangeSet operations on a stream. 034 * This class is thread safe and can be used multiple times. 035 * It operates on a copy of the ChangeSet. If the ChangeSet changes, 036 * a new Performer must be created. 037 * 038 * @ThreadSafe 039 * @Immutable 040 */ 041 public class ChangeSetPerformer { 042 private final Set<Change> changes; 043 044 /** 045 * Constructs a ChangeSetPerformer with the changes from this ChangeSet 046 * @param changeSet the ChangeSet which operations are used for performing 047 */ 048 public ChangeSetPerformer(final ChangeSet changeSet) { 049 changes = changeSet.getChanges(); 050 } 051 052 /** 053 * Performs all changes collected in this ChangeSet on the input stream and 054 * streams the result to the output stream. Perform may be called more than once. 055 * 056 * This method finishes the stream, no other entries should be added 057 * after that. 058 * 059 * @param in 060 * the InputStream to perform the changes on 061 * @param out 062 * the resulting OutputStream with all modifications 063 * @throws IOException 064 * if an read/write error occurs 065 * @return the results of this operation 066 */ 067 public ChangeSetResults perform(ArchiveInputStream in, ArchiveOutputStream out) 068 throws IOException { 069 ChangeSetResults results = new ChangeSetResults(); 070 071 Set<Change> workingSet = new LinkedHashSet<Change>(changes); 072 073 for (Iterator<Change> it = workingSet.iterator(); it.hasNext();) { 074 Change change = it.next(); 075 076 if (change.type() == Change.TYPE_ADD && change.isReplaceMode()) { 077 copyStream(change.getInput(), out, change.getEntry()); 078 it.remove(); 079 results.addedFromChangeSet(change.getEntry().getName()); 080 } 081 } 082 083 ArchiveEntry entry = null; 084 while ((entry = in.getNextEntry()) != null) { 085 boolean copy = true; 086 087 for (Iterator<Change> it = workingSet.iterator(); it.hasNext();) { 088 Change change = it.next(); 089 090 final int type = change.type(); 091 final String name = entry.getName(); 092 if (type == Change.TYPE_DELETE && name != null) { 093 if (name.equals(change.targetFile())) { 094 copy = false; 095 it.remove(); 096 results.deleted(name); 097 break; 098 } 099 } else if (type == Change.TYPE_DELETE_DIR && name != null) { 100 // don't combine ifs to make future extensions more easy 101 if (name.startsWith(change.targetFile() + "/")) { // NOPMD 102 copy = false; 103 results.deleted(name); 104 break; 105 } 106 } 107 } 108 109 if (copy 110 && !isDeletedLater(workingSet, entry) 111 && !results.hasBeenAdded(entry.getName())) { 112 copyStream(in, out, entry); 113 results.addedFromStream(entry.getName()); 114 } 115 } 116 117 // Adds files which hasn't been added from the original and do not have replace mode on 118 for (Iterator<Change> it = workingSet.iterator(); it.hasNext();) { 119 Change change = it.next(); 120 121 if (change.type() == Change.TYPE_ADD && 122 !change.isReplaceMode() && 123 !results.hasBeenAdded(change.getEntry().getName())) { 124 copyStream(change.getInput(), out, change.getEntry()); 125 it.remove(); 126 results.addedFromChangeSet(change.getEntry().getName()); 127 } 128 } 129 out.finish(); 130 return results; 131 } 132 133 /** 134 * Checks if an ArchiveEntry is deleted later in the ChangeSet. This is 135 * necessary if an file is added with this ChangeSet, but later became 136 * deleted in the same set. 137 * 138 * @param entry 139 * the entry to check 140 * @return true, if this entry has an deletion change later, false otherwise 141 */ 142 private boolean isDeletedLater(Set<Change> workingSet, ArchiveEntry entry) { 143 String source = entry.getName(); 144 145 if (!workingSet.isEmpty()) { 146 for (Change change : workingSet) { 147 final int type = change.type(); 148 String target = change.targetFile(); 149 if (type == Change.TYPE_DELETE && source.equals(target)) { 150 return true; 151 } 152 153 if (type == Change.TYPE_DELETE_DIR && source.startsWith(target + "/")){ 154 return true; 155 } 156 } 157 } 158 return false; 159 } 160 161 /** 162 * Copies the ArchiveEntry to the Output stream 163 * 164 * @param in 165 * the stream to read the data from 166 * @param out 167 * the stream to write the data to 168 * @param entry 169 * the entry to write 170 * @throws IOException 171 * if data cannot be read or written 172 */ 173 private void copyStream(InputStream in, ArchiveOutputStream out, 174 ArchiveEntry entry) throws IOException { 175 out.putArchiveEntry(entry); 176 IOUtils.copy(in, out); 177 out.closeArchiveEntry(); 178 } 179 }