    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;
    025import java.util.regex.Pattern;
    026
    027import org.apache.commons.compress.archivers.ArchiveEntry;
    028import org.apache.commons.compress.changes.Change.ChangeType;
    029
    030/**
    031 * ChangeSet collects and performs changes to an archive. Putting delete changes in this ChangeSet from multiple threads can cause conflicts.
    032 *
    033 * @param <E> The ArchiveEntry type.
    034 * @NotThreadSafe
    035 */
    036public final class ChangeSet<E extends ArchiveEntry> {
    037
    038    private final Set<Change<E>> changes = new LinkedHashSet<>();
    039
    040    /**
    041     * Adds a new archive entry to the archive.
    042     *
    043     * @param entry the entry to add
    044     * @param input the data stream to add
    045     */
    046    public void add(final E entry, final InputStream input) {
    047        this.add(entry, input, true);
    048    }
    049
    050    /**
    051     * Adds a new archive entry to the archive. If replace is set to true, this change will replace all other additions done in this ChangeSet and all existing
    052     * entries in the original stream.
    053     *
    054     * @param entry   the entry to add
    055     * @param input   the data stream to add
    056     * @param replace indicates the this change should replace existing entries
    057     */
    058    public void add(final E entry, final InputStream input, final boolean replace) {
    059        addAddition(new Change<>(entry, input, replace));
    060    }
    061
    062    /**
    063     * Adds an addition change.
    064     *
    065     * @param addChange the change which should result in an addition
    066     */
    067    @SuppressWarnings("resource") // InputStream is NOT allocated
    068    private void addAddition(final Change<E> addChange) {
    069        if (Change.ChangeType.ADD != addChange.getType() || addChange.getInputStream() == null) {
    070            return;
    071        }
    072
    073        if (!changes.isEmpty()) {
    074            for (final Iterator<Change<E>> it = changes.iterator(); it.hasNext();) {
    075                final Change<E> change = it.next();
    076                if (change.getType() == Change.ChangeType.ADD && change.getEntry() != null) {
    077                    final ArchiveEntry entry = change.getEntry();
    078
    079                    if (entry.equals(addChange.getEntry())) {
    080                        if (addChange.isReplaceMode()) {
    081                            it.remove();
    082                            changes.add(addChange);
    083                        }
    084                        // do not add this change
    085                        return;
    086                    }
    087                }
    088            }
    089        }
    090        changes.add(addChange);
    091    }
    092
    093    /**
    094     * Adds an delete change.
    095     *
    096     * @param deleteChange the change which should result in a deletion
    097     */
    098    private void addDeletion(final Change<E> deleteChange) {
    099        if (ChangeType.DELETE != deleteChange.getType() && ChangeType.DELETE_DIR != deleteChange.getType() || deleteChange.getTargetFileName() == null) {
    100            return;
    101        }
    102        final String source = deleteChange.getTargetFileName();
    103        final Pattern pattern = Pattern.compile(source + "/.*");
    104        if (source != null && !changes.isEmpty()) {
    105            for (final Iterator<Change<E>> it = changes.iterator(); it.hasNext();) {
    106                final Change<E> change = it.next();
    107                if (change.getType() == ChangeType.ADD && change.getEntry() != null) {
    108                    final String target = change.getEntry().getName();
    109                    if (target == null) {
    110                        continue;
    111                    }
    112                    if (ChangeType.DELETE == deleteChange.getType() && source.equals(target)
    113                            || ChangeType.DELETE_DIR == deleteChange.getType() && pattern.matcher(target).matches()) {
    114                        it.remove();
    115                    }
    116                }
    117            }
    118        }
    119        changes.add(deleteChange);
    120    }
    121
    122    /**
    123     * Deletes the file with the file name from the archive.
    124     *
    125     * @param fileName the file name of the file to delete
    126     */
    127    public void delete(final String fileName) {
    128        addDeletion(new Change<>(fileName, ChangeType.DELETE));
    129    }
    130
    131    /**
    132     * Deletes the directory tree from the archive.
    133     *
    134     * @param dirName the name of the directory tree to delete
    135     */
    136    public void deleteDir(final String dirName) {
    137        addDeletion(new Change<>(dirName, ChangeType.DELETE_DIR));
    138    }
    139
    140    /**
    141     * Gets the list of changes as a copy. Changes on this set are not reflected on this ChangeSet and vice versa.
    142     *
    143     * @return the changes as a copy
    144     */
    145    Set<Change<E>> getChanges() {
    146        return new LinkedHashSet<>(changes);
    147    }
    148}