Skip to content

Commit d47f3ab

Browse files
#89 Add file or folder to specific path in zip
1 parent 41922e6 commit d47f3ab

40 files changed

+739
-637
lines changed

build.gradle

+4-2
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,9 @@ dependencies {
6666
testImplementation 'net.sf.sevenzipjbinding:sevenzipjbinding-all-platforms:16.02-2.01'
6767
testImplementation 'net.lingala.zip4j:zip4j:2.11.5'
6868
//noinspection VulnerableLibrariesLocal
69-
testImplementation 'de.idyl:winzipaes:1.0.1'
69+
testImplementation('de.idyl:winzipaes:1.0.1') {
70+
exclude group: 'org.bouncycastle', module: 'bcprov-jdk16'
71+
}
7072
testImplementation 'org.tukaani:xz:1.10'
7173
}
7274

@@ -81,7 +83,7 @@ javadoc {
8183
options.addBooleanOption('Xdoclint:-missing', true)
8284
}
8385

84-
check.finalizedBy(checkLicense)
86+
//check.finalizedBy(checkLicense)
8587
check.finalizedBy(generateLicenseReport)
8688
check.finalizedBy(jacocoTestReport)
8789

src/main/java/ru/olegcherednik/zip4jvm/ZipFile.java

+4-5
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
import ru.olegcherednik.zip4jvm.model.settings.ZipSettings;
3030
import ru.olegcherednik.zip4jvm.model.src.SrcZip;
3131
import ru.olegcherednik.zip4jvm.utils.EmptyInputStreamSupplier;
32-
import ru.olegcherednik.zip4jvm.utils.PathUtils;
3332
import ru.olegcherednik.zip4jvm.utils.quitely.Quietly;
3433
import ru.olegcherednik.zip4jvm.utils.quitely.functions.InputStreamSupplier;
3534

@@ -125,11 +124,11 @@ public InputStream getInputStream() {
125124

126125
public interface Writer extends Closeable {
127126

128-
default void add(Path path) {
129-
add(path, PathUtils.getName(path));
130-
}
127+
void add(Path path);
128+
129+
void addWithRename(Path path, String name);
131130

132-
void add(Path path, String name);
131+
void addWithMove(Path path, String dir);
133132

134133
void add(ZipFile.Entry entry);
135134

src/main/java/ru/olegcherednik/zip4jvm/ZipIt.java

+28-7
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020

2121
import ru.olegcherednik.zip4jvm.exception.PathNotExistsException;
2222
import ru.olegcherednik.zip4jvm.model.settings.ZipEntrySettings;
23+
import ru.olegcherednik.zip4jvm.model.settings.ZipEntrySettingsProvider;
2324
import ru.olegcherednik.zip4jvm.model.settings.ZipSettings;
24-
import ru.olegcherednik.zip4jvm.utils.ValidationUtils;
2525

2626
import lombok.AccessLevel;
2727
import lombok.RequiredArgsConstructor;
@@ -32,7 +32,6 @@
3232
import java.util.Collection;
3333
import java.util.Collections;
3434
import java.util.Optional;
35-
import java.util.function.Function;
3635

3736
import static ru.olegcherednik.zip4jvm.utils.ValidationUtils.requireExists;
3837
import static ru.olegcherednik.zip4jvm.utils.ValidationUtils.requireNotNull;
@@ -86,19 +85,19 @@ public ZipIt settings(ZipSettings settings) {
8685
* @return not {@literal null} {@link ZipIt} instance
8786
*/
8887
public ZipIt entrySettings(ZipEntrySettings entrySettings) {
89-
return entrySettings == null ? entrySettings(ZipEntrySettings.DEFAULT_PROVIDER)
90-
: entrySettings(fileName -> entrySettings);
88+
return entrySettings == null ? entrySettings(ZipEntrySettingsProvider.DEFAULT)
89+
: entrySettings(ZipEntrySettingsProvider.of(entrySettings));
9190
}
9291

9392
/**
9493
* Set provider of {@link ZipEntrySettings} for the given file name. Each entry could have separate settings. If
9594
* {@literal null}, then {@link
96-
* ZipEntrySettings#DEFAULT_PROVIDER} will be used.
95+
* ZipEntrySettingsProvider#DEFAULT} will be used.
9796
*
9897
* @param entrySettingsProvider entry settings provider with fileName as a key
9998
* @return not {@literal null} {@link ZipIt} instance
10099
*/
101-
public ZipIt entrySettings(Function<String, ZipEntrySettings> entrySettingsProvider) {
100+
public ZipIt entrySettings(ZipEntrySettingsProvider entrySettingsProvider) {
102101
requireNotNull(entrySettingsProvider, "ZipIt.entrySettingsProvider");
103102
settings = settings.toBuilder().entrySettingsProvider(entrySettingsProvider).build();
104103
return this;
@@ -128,7 +127,7 @@ public void add(Collection<Path> paths) throws IOException {
128127
if (CollectionUtils.isEmpty(paths))
129128
return;
130129

131-
paths.forEach(ValidationUtils::requireExists);
130+
requireExists(paths);
132131

133132
// TODO check that path != zip
134133
try (ZipFile.Writer zipFile = ZipFile.writer(zip, settings)) {
@@ -137,6 +136,28 @@ public void add(Collection<Path> paths) throws IOException {
137136
}
138137
}
139138

139+
/**
140+
* Add regular file or directory (keeping initial structure) to the new or existed zip archive under give
141+
* {@code name}.<br>
142+
* In case given {@code path} is a directory (or symlink to directory), then this directory will be renamed.<br>
143+
* In case given {@code path} is a regular file (or symlink to the file), then this file will be renamed.
144+
*
145+
* @param path path to the regular file or directory
146+
* @param name not {@literal null} name to be used for the {@code path}
147+
* @throws IOException in case of any problem with file access
148+
*/
149+
public void addWithRename(Path path, String name) throws IOException {
150+
try (ZipFile.Writer zipFile = ZipFile.writer(zip, settings)) {
151+
zipFile.addWithRename(path, name);
152+
}
153+
}
154+
155+
public void addWithMove(Path path, String dir) throws IOException {
156+
try (ZipFile.Writer zipFile = ZipFile.writer(zip, settings)) {
157+
zipFile.addWithMove(path, dir);
158+
}
159+
}
160+
140161
/**
141162
* Creates instance of zip file stream. It could be used to add multiple entries to the zip archive. It should be
142163
* correctly closed to flush all data.

src/main/java/ru/olegcherednik/zip4jvm/engine/ZipEngine.java

+47-26
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import ru.olegcherednik.zip4jvm.utils.function.Writer;
4040

4141
import lombok.extern.slf4j.Slf4j;
42+
import org.apache.commons.lang3.StringUtils;
4243

4344
import java.io.IOException;
4445
import java.nio.file.Files;
@@ -74,44 +75,64 @@ public ZipEngine(Path zip, ZipSettings settings) throws IOException {
7475
}
7576

7677
@Override
77-
public void add(Path path, String name) {
78+
public void add(Path path) {
79+
add(path, PathUtils.getName(path), "");
80+
}
81+
82+
@Override
83+
public void addWithRename(Path path, String name) {
84+
add(path, name, "");
85+
}
86+
87+
@Override
88+
public void addWithMove(Path path, String dir) {
89+
add(path, PathUtils.getName(path), dir);
90+
}
91+
92+
private void add(Path path, String name, String dir) {
7893
if (!Files.exists(path))
7994
return;
8095

8196
if (Files.isSymbolicLink(path))
8297
path = ZipSymlinkEngine.getSymlinkTarget(path);
8398

84-
if (Files.isDirectory(path))
85-
zipSymlinkEngine.list(getDirectoryNamedPaths(path, name)).stream()
86-
.map(namedPath -> {
87-
String entryName = namedPath.getEntryName();
88-
ZipEntrySettings entrySettings = getEntrySettings(entryName);
89-
return namedPath.createZipEntry(entrySettings);
90-
})
91-
.forEach(this::add);
92-
else if (Files.isRegularFile(path)) {
93-
ZipEntrySettings entrySettings = getEntrySettings(name);
94-
ZipEntry zipEntry = ZipEntryBuilder.regularFile(path, name, entrySettings);
95-
add(zipEntry);
96-
} else
97-
log.warn("Unknown path type '{}'; ignore it", path);
99+
for (NamedPath namedPath : getNamedPaths(path, name, dir)) {
100+
String entryName = namedPath.getEntryName();
101+
ZipEntrySettings entrySettings = settings.getEntrySettings(entryName);
102+
add(namedPath.createZipEntry(entrySettings));
103+
}
98104
}
99105

100-
private ZipEntrySettings getEntrySettings(String entryName) {
101-
return settings.getEntrySettingsProvider().apply(entryName);
106+
private List<NamedPath> getNamedPaths(Path path, String name, String dir) {
107+
if (Files.isDirectory(path))
108+
return zipSymlinkEngine.list(getDirectoryNamedPaths(path, name, dir));
109+
110+
if (Files.isRegularFile(path)) {
111+
if (StringUtils.isNotBlank(dir))
112+
name = dir + '/' + name;
113+
114+
return Collections.singletonList(NamedPath.create(path, name));
115+
}
116+
117+
log.warn("Unknown path type '{}'; ignore it", path);
118+
return Collections.emptyList();
102119
}
103120

104-
private List<NamedPath> getDirectoryNamedPaths(Path path, String name) {
105-
return settings.isRemoveRootDir() ? PathUtils.list(path).stream()
106-
.map(NamedPath::create)
107-
.sorted(NamedPath.SORT_BY_NAME_ASC)
108-
.collect(Collectors.toList())
109-
: Collections.singletonList(NamedPath.create(path, name));
121+
private List<NamedPath> getDirectoryNamedPaths(Path path, String name, String dir) {
122+
if (settings.isRemoveRootDir())
123+
return PathUtils.list(path).stream()
124+
.map(p -> StringUtils.isNotBlank(dir)
125+
? NamedPath.create(p, dir + '/' + PathUtils.getName(p))
126+
: NamedPath.create(p))
127+
.sorted(NamedPath.SORT_BY_NAME_ASC)
128+
.collect(Collectors.toList());
129+
130+
return Collections.singletonList(NamedPath.create(path, name));
110131
}
111132

112133
@Override
113134
public void add(ZipFile.Entry entry) {
114-
ZipEntrySettings entrySettings = settings.getEntrySettingsProvider().apply(entry.getName());
135+
ZipEntrySettings entrySettings = settings.getEntrySettings(entry.getName());
115136
ZipEntry zipEntry = ZipEntryBuilder.build(entry, entrySettings);
116137
add(zipEntry);
117138
}
@@ -164,7 +185,7 @@ public void copy(Path zip) throws IOException {
164185
if (fileNameWriter.containsKey(fileName))
165186
throw new EntryDuplicationException(fileName);
166187

167-
char[] password = settings.getEntrySettingsProvider().apply(fileName).getPassword();
188+
char[] password = settings.getEntrySettings(fileName).getPassword();
168189
fileNameWriter.put(fileName, new ExistedEntryWriter(srcZipModel, fileName, tempZipModel, password));
169190
}
170191
}
@@ -224,7 +245,7 @@ private static ZipModel createTempZipModel(Path zip, ZipSettings settings, Map<S
224245
tempZipModel.setZip64(zipModel.isZip64());
225246

226247
zipModel.getEntryNames().forEach(entryName -> {
227-
char[] password = settings.getEntrySettingsProvider().apply(entryName).getPassword();
248+
char[] password = settings.getEntrySettings(entryName).getPassword();
228249
fileNameWriter.put(entryName, new ExistedEntryWriter(zipModel, entryName, tempZipModel, password));
229250
});
230251
}

src/main/java/ru/olegcherednik/zip4jvm/model/settings/ZipEntrySettings.java

+37-10
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,13 @@
2323
import ru.olegcherednik.zip4jvm.model.CompressionLevel;
2424
import ru.olegcherednik.zip4jvm.model.Encryption;
2525
import ru.olegcherednik.zip4jvm.model.ZipModel;
26-
import ru.olegcherednik.zip4jvm.utils.ValidationUtils;
2726

2827
import lombok.AccessLevel;
2928
import lombok.Getter;
3029
import lombok.NoArgsConstructor;
3130
import org.apache.commons.lang3.ArrayUtils;
3231

33-
import java.util.function.Function;
32+
import static ru.olegcherednik.zip4jvm.utils.ValidationUtils.requireLengthLessOrEqual;
3433

3534
/**
3635
* @author Oleg Cherednik
@@ -41,7 +40,6 @@
4140
public final class ZipEntrySettings {
4241

4342
public static final ZipEntrySettings DEFAULT = builder().build();
44-
public static final Function<String, ZipEntrySettings> DEFAULT_PROVIDER = fileName -> DEFAULT;
4543

4644
private final Compression compression;
4745
private final CompressionLevel compressionLevel;
@@ -56,6 +54,26 @@ public static Builder builder() {
5654
return new Builder();
5755
}
5856

57+
public static ZipEntrySettings of(Compression compression) {
58+
if (compression == DEFAULT.getCompression())
59+
return DEFAULT;
60+
return builder().compression(compression).build();
61+
}
62+
63+
public static ZipEntrySettings of(Compression compression, Encryption encryption, char[] password) {
64+
if (encryption == Encryption.OFF)
65+
return of(compression);
66+
return builder()
67+
.compression(compression)
68+
.encryption(encryption, password).build();
69+
}
70+
71+
public static ZipEntrySettings of(Encryption encryption, char[] password) {
72+
if (encryption == Encryption.OFF)
73+
return of(DEFAULT.getCompression());
74+
return builder().encryption(encryption, password).build();
75+
}
76+
5977
public Builder toBuilder() {
6078
return new Builder(this);
6179
}
@@ -75,6 +93,7 @@ private ZipEntrySettings(Builder builder) {
7593
@SuppressWarnings("PMD.UnusedAssignment")
7694
public static final class Builder {
7795

96+
7897
private Compression compression = Compression.DEFLATE;
7998
private CompressionLevel compressionLevel = CompressionLevel.NORMAL;
8099
private Encryption encryption = Encryption.OFF;
@@ -102,30 +121,38 @@ public ZipEntrySettings build() {
102121
return new ZipEntrySettings(this);
103122
}
104123

124+
public ZipEntrySettings.Builder compression(Compression compression) {
125+
return compression(compression, CompressionLevel.NORMAL);
126+
}
127+
105128
public ZipEntrySettings.Builder compression(Compression compression, CompressionLevel compressionLevel) {
106129
this.compression = compression;
107130
this.compressionLevel = compressionLevel;
108131
return this;
109132
}
110133

111134
public ZipEntrySettings.Builder encryption(Encryption encryption, char[] password) {
112-
if (encryption != Encryption.OFF) {
113-
this.encryption = encryption;
135+
this.encryption = encryption;
136+
137+
if (encryption == Encryption.OFF)
138+
this.password = null;
139+
else {
140+
if (ArrayUtils.isEmpty(password))
141+
throw new EmptyPasswordException();
142+
114143
this.password = ArrayUtils.clone(password);
115144
}
116145

117146
return this;
118147
}
119148

120149
public ZipEntrySettings.Builder password(char[] password) {
121-
this.password = ArrayUtils.clone(password);
122-
return this;
150+
return encryption(encryption, password);
123151
}
124152

125153
public ZipEntrySettings.Builder comment(String comment) {
126-
this.comment = ValidationUtils.requireLengthLessOrEqual(comment,
127-
ZipModel.MAX_COMMENT_SIZE,
128-
"ZipEntry.comment");
154+
requireLengthLessOrEqual(comment, ZipModel.MAX_COMMENT_SIZE, "ZipEntry.comment");
155+
this.comment = comment;
129156
return this;
130157
}
131158

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package ru.olegcherednik.zip4jvm.model.settings;
20+
21+
import lombok.AccessLevel;
22+
import lombok.RequiredArgsConstructor;
23+
24+
import java.util.Optional;
25+
import java.util.function.Function;
26+
27+
/**
28+
* @author Oleg Cherednik
29+
* @since 20.10.2024
30+
*/
31+
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
32+
public final class ZipEntrySettingsProvider {
33+
34+
public static final ZipEntrySettingsProvider DEFAULT = of(ZipEntrySettings.DEFAULT);
35+
36+
private final Function<String, ZipEntrySettings> entryNameSettings;
37+
38+
public static ZipEntrySettingsProvider of(ZipEntrySettings entrySettings) {
39+
return new ZipEntrySettingsProvider(entryName -> entrySettings);
40+
}
41+
42+
public static ZipEntrySettingsProvider of(Function<String, ZipEntrySettings> entryNameSettings) {
43+
return new ZipEntrySettingsProvider(entryNameSettings);
44+
}
45+
46+
// @NotNull
47+
public ZipEntrySettings getEntrySettings(String entryName) {
48+
return Optional.ofNullable(entryNameSettings.apply(entryName))
49+
.orElse(ZipEntrySettings.DEFAULT);
50+
}
51+
52+
}

0 commit comments

Comments
 (0)