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 */ 019package org.apache.commons.compress.changes; 020 021import java.io.InputStream; 022import java.util.Iterator; 023import java.util.LinkedHashSet; 024import java.util.Set; 025import java.util.regex.Pattern; 026 027import org.apache.commons.compress.archivers.ArchiveEntry; 028import org.apache.commons.compress.changes.Change.ChangeType; 029 030/** 031 * ChangeSet collects and performs changes to an archive. Putting delete changes in this ChangeSet from multiple threads can cause conflicts. 032 * 033 * @param <E> The ArchiveEntry type. 034 * @NotThreadSafe 035 */ 036public final class ChangeSet<E extends ArchiveEntry> { 037 038 private final Set<Change<E>> changes = new LinkedHashSet<>(); 039 040 /** 041 * Adds a new archive entry to the archive. 042 * 043 * @param entry the entry to add 044 * @param input the data stream to add 045 */ 046 public void add(final E entry, final InputStream input) { 047 this.add(entry, input, true); 048 } 049 050 /** 051 * 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 052 * entries in the original stream. 053 * 054 * @param entry the entry to add 055 * @param input the data stream to add 056 * @param replace indicates the this change should replace existing entries 057 */ 058 public void add(final E entry, final InputStream input, final boolean replace) { 059 addAddition(new Change<>(entry, input, replace)); 060 } 061 062 /** 063 * Adds an addition change. 064 * 065 * @param addChange the change which should result in an addition 066 */ 067 @SuppressWarnings("resource") // InputStream is NOT allocated 068 private void addAddition(final Change<E> addChange) { 069 if (Change.ChangeType.ADD != addChange.getType() || addChange.getInputStream() == null) { 070 return; 071 } 072 073 if (!changes.isEmpty()) { 074 for (final Iterator<Change<E>> it = changes.iterator(); it.hasNext();) { 075 final Change<E> change = it.next(); 076 if (change.getType() == Change.ChangeType.ADD && change.getEntry() != null) { 077 final ArchiveEntry entry = change.getEntry(); 078 079 if (entry.equals(addChange.getEntry())) { 080 if (addChange.isReplaceMode()) { 081 it.remove(); 082 changes.add(addChange); 083 } 084 // do not add this change 085 return; 086 } 087 } 088 } 089 } 090 changes.add(addChange); 091 } 092 093 /** 094 * Adds an delete change. 095 * 096 * @param deleteChange the change which should result in a deletion 097 */ 098 private void addDeletion(final Change<E> deleteChange) { 099 if (ChangeType.DELETE != deleteChange.getType() && ChangeType.DELETE_DIR != deleteChange.getType() || deleteChange.getTargetFileName() == null) { 100 return; 101 } 102 final String source = deleteChange.getTargetFileName(); 103 final Pattern pattern = Pattern.compile(source + "/.*"); 104 if (source != null && !changes.isEmpty()) { 105 for (final Iterator<Change<E>> it = changes.iterator(); it.hasNext();) { 106 final Change<E> change = it.next(); 107 if (change.getType() == ChangeType.ADD && change.getEntry() != null) { 108 final String target = change.getEntry().getName(); 109 if (target == null) { 110 continue; 111 } 112 if (ChangeType.DELETE == deleteChange.getType() && source.equals(target) 113 || ChangeType.DELETE_DIR == deleteChange.getType() && pattern.matcher(target).matches()) { 114 it.remove(); 115 } 116 } 117 } 118 } 119 changes.add(deleteChange); 120 } 121 122 /** 123 * Deletes the file with the file name from the archive. 124 * 125 * @param fileName the file name of the file to delete 126 */ 127 public void delete(final String fileName) { 128 addDeletion(new Change<>(fileName, ChangeType.DELETE)); 129 } 130 131 /** 132 * Deletes the directory tree from the archive. 133 * 134 * @param dirName the name of the directory tree to delete 135 */ 136 public void deleteDir(final String dirName) { 137 addDeletion(new Change<>(dirName, ChangeType.DELETE_DIR)); 138 } 139 140 /** 141 * Gets the list of changes as a copy. Changes on this set are not reflected on this ChangeSet and vice versa. 142 * 143 * @return the changes as a copy 144 */ 145 Set<Change<E>> getChanges() { 146 return new LinkedHashSet<>(changes); 147 } 148}