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 * https://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 * Constructs a new instance. 042 */ 043 public ChangeSet() { 044 // empty 045 } 046 047 /** 048 * Adds a new archive entry to the archive. 049 * 050 * @param entry the entry to add 051 * @param input the data stream to add 052 */ 053 public void add(final E entry, final InputStream input) { 054 this.add(entry, input, true); 055 } 056 057 /** 058 * 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 059 * entries in the original stream. 060 * 061 * @param entry the entry to add 062 * @param input the data stream to add 063 * @param replace indicates the this change should replace existing entries 064 */ 065 public void add(final E entry, final InputStream input, final boolean replace) { 066 addAddition(new Change<>(entry, input, replace)); 067 } 068 069 /** 070 * Adds an addition change. 071 * 072 * @param addChange the change which should result in an addition 073 */ 074 @SuppressWarnings("resource") // InputStream is NOT allocated 075 private void addAddition(final Change<E> addChange) { 076 if (Change.ChangeType.ADD != addChange.getType() || addChange.getInputStream() == null) { 077 return; 078 } 079 080 if (!changes.isEmpty()) { 081 for (final Iterator<Change<E>> it = changes.iterator(); it.hasNext();) { 082 final Change<E> change = it.next(); 083 if (change.getType() == Change.ChangeType.ADD && change.getEntry() != null) { 084 final ArchiveEntry entry = change.getEntry(); 085 086 if (entry.equals(addChange.getEntry())) { 087 if (addChange.isReplaceMode()) { 088 it.remove(); 089 changes.add(addChange); 090 } 091 // do not add this change 092 return; 093 } 094 } 095 } 096 } 097 changes.add(addChange); 098 } 099 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}