    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     */
    019    package org.apache.commons.compress.changes;
    020
    021    import java.io.InputStream;
    022    import java.util.Iterator;
    023    import java.util.LinkedHashSet;
    024    import java.util.Set;
    025
    026    import 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     */
    035    public final class ChangeSet {
    036
    037        private final Set<Change> changes = new LinkedHashSet<Change>();
    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(Change pChange) {
    094            if (Change.TYPE_ADD != pChange.type() ||
    095                pChange.getInput() == null) {
    096                return;
    097            }
    098
    099            if (!changes.isEmpty()) {
    100                for (Iterator<Change> it = changes.iterator(); it.hasNext();) {
    101                    Change change = it.next();
    102                    if (change.type() == Change.TYPE_ADD
    103                            && change.getEntry() != null) {
    104                        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                            } else {
    112                                // do not add this change
    113                                return;
    114                            }
    115                        }
    116                    }
    117                }
    118            }
    119            changes.add(pChange);
    120        }
    121
    122        /**
    123         * Adds an delete change.
    124         *
    125         * @param pChange
    126         *            the change which should result in a deletion
    127         */
    128        private void addDeletion(Change pChange) {
    129            if ((Change.TYPE_DELETE != pChange.type() &&
    130                Change.TYPE_DELETE_DIR != pChange.type()) ||
    131                pChange.targetFile() == null) {
    132                return;
    133            }
    134            String source = pChange.targetFile();
    135
    136            if (!changes.isEmpty()) {
    137                for (Iterator<Change> it = changes.iterator(); it.hasNext();) {
    138                    Change change = it.next();
    139                    if (change.type() == Change.TYPE_ADD
    140                            && change.getEntry() != null) {
    141                        String target = change.getEntry().getName();
    142
    143                        if (Change.TYPE_DELETE == pChange.type() && source.equals(target)) {
    144                            it.remove();
    145                        } else if (Change.TYPE_DELETE_DIR == pChange.type() &&
    146                                   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<Change>(changes);
    162        }
    163    }