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 * Adds a new archive entry to the archive. 041 * 042 * @param pEntry 043 * the entry to add 044 * @param pInput 045 * the datastream to add 046 */ 047 public void add(final ArchiveEntry pEntry, final InputStream pInput) { 048 this.add(pEntry, pInput, true); 049 } 050 051 /** 052 * Adds a new archive entry to the archive. 053 * If replace is set to true, this change will replace all other additions 054 * done in this ChangeSet and all existing entries in the original stream. 055 * 056 * @param pEntry 057 * the entry to add 058 * @param pInput 059 * the datastream to add 060 * @param replace 061 * indicates the this change should replace existing entries 062 */ 063 public void add(final ArchiveEntry pEntry, final InputStream pInput, final boolean replace) { 064 addAddition(new Change(pEntry, pInput, replace)); 065 } 066 067 /** 068 * Adds an addition change. 069 * 070 * @param pChange 071 * the change which should result in an addition 072 */ 073 private void addAddition(final Change pChange) { 074 if (Change.TYPE_ADD != pChange.type() || 075 pChange.getInput() == null) { 076 return; 077 } 078 079 if (!changes.isEmpty()) { 080 for (final Iterator<Change> it = changes.iterator(); it.hasNext();) { 081 final Change change = it.next(); 082 if (change.type() == Change.TYPE_ADD 083 && change.getEntry() != null) { 084 final ArchiveEntry entry = change.getEntry(); 085 086 if (entry.equals(pChange.getEntry())) { 087 if (pChange.isReplaceMode()) { 088 it.remove(); 089 changes.add(pChange); 090 } 091 // do not add this change 092 return; 093 } 094 } 095 } 096 } 097 changes.add(pChange); 098 } 099 100 /** 101 * Adds an delete change. 102 * 103 * @param pChange 104 * the change which should result in a deletion 105 */ 106 private void addDeletion(final Change pChange) { 107 if ((Change.TYPE_DELETE != pChange.type() && 108 Change.TYPE_DELETE_DIR != pChange.type()) || 109 pChange.targetFile() == null) { 110 return; 111 } 112 final String source = pChange.targetFile(); 113 114 if (source != null && !changes.isEmpty()) { 115 for (final Iterator<Change> it = changes.iterator(); it.hasNext();) { 116 final Change change = it.next(); 117 if (change.type() == Change.TYPE_ADD 118 && change.getEntry() != null) { 119 final String target = change.getEntry().getName(); 120 121 if (target == null) { 122 continue; 123 } 124 125 if (Change.TYPE_DELETE == pChange.type() && source.equals(target) || 126 (Change.TYPE_DELETE_DIR == pChange.type() && target.matches(source + "/.*"))) { 127 it.remove(); 128 } 129 } 130 } 131 } 132 changes.add(pChange); 133 } 134 135 /** 136 * Deletes the file with the file name from the archive. 137 * 138 * @param fileName 139 * the file name of the file to delete 140 */ 141 public void delete(final String fileName) { 142 addDeletion(new Change(fileName, Change.TYPE_DELETE)); 143 } 144 145 /** 146 * Deletes the directory tree from the archive. 147 * 148 * @param dirName 149 * the name of the directory tree to delete 150 */ 151 public void deleteDir(final String dirName) { 152 addDeletion(new Change(dirName, Change.TYPE_DELETE_DIR)); 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}