    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}