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; 025 026import 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 */ 035public final class ChangeSet { 036 037 private final Set<Change> changes = new LinkedHashSet<>(); 038 039 /** 040 * Deletes the file with the file name from the archive. 041 * 042 * @param fileName 043 * the file name 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(final Change pChange) { 094 if (Change.TYPE_ADD != pChange.type() || 095 pChange.getInput() == null) { 096 return; 097 } 098 099 if (!changes.isEmpty()) { 100 for (final Iterator<Change> it = changes.iterator(); it.hasNext();) { 101 final Change change = it.next(); 102 if (change.type() == Change.TYPE_ADD 103 && change.getEntry() != null) { 104 final ArchiveEntry entry = change.getEntry(); 105 106 if(entry.equals(pChange.getEntry())) { 107 if(pChange.isReplaceMode()) { 108 it.remove(); 109 changes.add(pChange); 110 } 111 // do not add this change 112 return; 113 } 114 } 115 } 116 } 117 changes.add(pChange); 118 } 119 120 /** 121 * Adds an delete change. 122 * 123 * @param pChange 124 * the change which should result in a deletion 125 */ 126 private void addDeletion(final Change pChange) { 127 if ((Change.TYPE_DELETE != pChange.type() && 128 Change.TYPE_DELETE_DIR != pChange.type()) || 129 pChange.targetFile() == null) { 130 return; 131 } 132 final String source = pChange.targetFile(); 133 134 if (source != null && !changes.isEmpty()) { 135 for (final Iterator<Change> it = changes.iterator(); it.hasNext();) { 136 final Change change = it.next(); 137 if (change.type() == Change.TYPE_ADD 138 && change.getEntry() != null) { 139 final String target = change.getEntry().getName(); 140 141 if (target == null) { 142 continue; 143 } 144 145 if (Change.TYPE_DELETE == pChange.type() && source.equals(target) || 146 (Change.TYPE_DELETE_DIR == pChange.type() && 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<>(changes); 162 } 163}