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 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(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 return; 111 } 112 // do not add this change 113 return; 114 } 115 } 116 } 117 } 118 changes.add(pChange); 119 } 120 121 /** 122 * Adds an delete change. 123 * 124 * @param pChange 125 * the change which should result in a deletion 126 */ 127 private void addDeletion(final Change pChange) { 128 if ((Change.TYPE_DELETE != pChange.type() && 129 Change.TYPE_DELETE_DIR != pChange.type()) || 130 pChange.targetFile() == null) { 131 return; 132 } 133 final String source = pChange.targetFile(); 134 135 if (source != null && !changes.isEmpty()) { 136 for (final Iterator<Change> it = changes.iterator(); it.hasNext();) { 137 final Change change = it.next(); 138 if (change.type() == Change.TYPE_ADD 139 && change.getEntry() != null) { 140 final String target = change.getEntry().getName(); 141 142 if (target == null) { 143 continue; 144 } 145 146 if (Change.TYPE_DELETE == pChange.type() && source.equals(target)) { 147 it.remove(); 148 } else if (Change.TYPE_DELETE_DIR == pChange.type() && 149 target.matches(source + "/.*")) { 150 it.remove(); 151 } 152 } 153 } 154 } 155 changes.add(pChange); 156 } 157 158 /** 159 * Returns the list of changes as a copy. Changes on this set 160 * are not reflected on this ChangeSet and vice versa. 161 * @return the changes as a copy 162 */ 163 Set<Change> getChanges() { 164 return new LinkedHashSet<>(changes); 165 } 166}