IKVM11  11
Java SE 11 Virtual Machine for .NET
Loading...
Searching...
No Matches
JarFile.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.IO.Compression;
5using System.Threading;
6
8
10{
11
15 internal class JarFile : IDisposable
16 {
17
18 const string META_INF = "META-INF/";
19 const string META_INF_VERSIONS = META_INF + "versions/";
20 const string MANIFEST_NAME = META_INF + "MANIFEST.MF";
21 const string INDEX_NAME = "META-INF/INDEX.LIST";
22
23 internal static readonly RuntimeVersion BASE_VERSION = RuntimeVersion.Parse("8");
24 internal static readonly int BASE_VERSION_FEATURE = BASE_VERSION.Feature;
25 internal static readonly RuntimeVersion RUNTIME_VERSION = RuntimeVersion.Parse("8");
26 internal static readonly bool MULTI_RELEASE_ENABLED = false;
27 internal static readonly bool MULTI_RELEASE_FORCED = false;
28
29 readonly ZipArchive _archive;
30 readonly RuntimeVersion _version;
31 readonly int _versionFeature;
32 readonly bool _dispose;
33
34 Manifest? _manifest;
35 bool _hasCheckedSpecialAttributes;
36 bool _hasClassPathAttribute;
37 bool _isMultiRelease;
38
45 public JarFile(ZipArchive archive, bool dispose = false) :
46 this(archive, RUNTIME_VERSION, dispose)
47 {
48
49 }
50
58 public JarFile(ZipArchive archive, RuntimeVersion version, bool dispose = false)
59 {
60 _archive = archive ?? throw new ArgumentNullException(nameof(archive));
61 _dispose = dispose;
62
63 if (MULTI_RELEASE_FORCED || version.Feature == RUNTIME_VERSION.Feature)
64 {
65 _version = RUNTIME_VERSION;
66 }
67 else if (version.Feature <= BASE_VERSION_FEATURE)
68 {
69 _version = BASE_VERSION;
70 }
71 else
72 {
73 _version = RuntimeVersion.Parse(version.Feature.ToString());
74 }
75
76 _versionFeature = _version.Feature;
77 }
78
84 public JarFile(Stream stream, bool dispose = true) :
85 this(stream, RUNTIME_VERSION, dispose)
86 {
87
88 }
89
96 public JarFile(Stream stream, RuntimeVersion releaseVersion, bool dispose = true) :
97 this(new ZipArchive(stream, ZipArchiveMode.Read, dispose), releaseVersion, dispose)
98 {
99
100 }
101
106 public JarFile(string path) :
107 this(path, RUNTIME_VERSION)
108 {
109
110 }
111
117 public JarFile(string path, RuntimeVersion releaseVersion) :
118 this(File.OpenRead(path), releaseVersion, true)
119 {
120
121 }
122
126 public RuntimeVersion Version => IsMultiRelease ? _version : BASE_VERSION;
127
131 public Manifest? Manifest => GetOrReadManifest();
132
137 Manifest? GetOrReadManifest()
138 {
139 if (_manifest is null)
140 Interlocked.CompareExchange(ref _manifest, ReadManifest(), null);
141
142 return _manifest;
143 }
144
149 Manifest? ReadManifest()
150 {
151 // find the manifest in the archive
152 var e = _archive.GetEntry(MANIFEST_NAME);
153 if (e == null)
154 return null;
155
156 // open and read the manifest
157 using var s = e.Open();
158 return new Manifest(s);
159 }
160
164 void CheckForSpecialAttributes()
165 {
166 if (_hasCheckedSpecialAttributes)
167 return;
168
169 if (Manifest is not null)
170 {
171 if (Manifest.MainAttributes.TryGetValue("Class-Path", out _))
172 _hasClassPathAttribute = true;
173
174 if (Manifest.MainAttributes.TryGetValue("Multi-Release", out var s))
175 _isMultiRelease = bool.Parse(s);
176 }
177
178 _hasCheckedSpecialAttributes = true;
179 }
180
184 public bool HasClassPathAttribute
185 {
186 get
187 {
188 if (_hasClassPathAttribute)
189 return true;
190
191 CheckForSpecialAttributes();
192 return _hasClassPathAttribute;
193 }
194 }
195
199 public bool IsMultiRelease
200 {
201 get
202 {
203 if (_isMultiRelease)
204 return true;
205
206 CheckForSpecialAttributes();
207 return _isMultiRelease;
208 }
209 }
210
214 public IEnumerable<JarFileEntry> GetEntries()
215 {
216 var hs = new HashSet<string>();
217
218 foreach (var entry in _archive.Entries)
219 {
220 var bn = GetBaseName(entry.FullName);
221 if (bn is null)
222 continue;
223
224 if (hs.Add(bn))
225 {
226 var je = GetEntry(bn);
227 if (je is not null)
228 yield return je.Value;
229 }
230 }
231 }
232
238 string? GetBaseName(string name)
239 {
240 if (name.StartsWith(META_INF_VERSIONS))
241 {
242 int off = META_INF_VERSIONS.Length;
243 int idx = name.IndexOf('/', off);
244
245 // filter out dir META-INF/versions/ and META-INF/versions/*/
246 // and any entry with version > 'version'
247 if (idx == -1 || idx == (name.Length - 1) || (int.TryParse(name[off..idx], out var i) && i > _versionFeature))
248 return null;
249
250 // map to its base name
251 return name.Substring(idx + 1);
252 }
253
254 return name;
255 }
256
262 public JarFileEntry? GetEntry(string name)
263 {
264 if (IsMultiRelease)
265 {
266 var ve = GetVersionedEntry(name, null);
267 if (ve is not null)
268 return ve;
269 }
270
271 var je = _archive.GetEntry(name);
272 if (je is not null)
273 return new JarFileEntry(this, je, name, _versionFeature);
274
275 return null;
276 }
277
284 JarFileEntry? GetVersionedEntry(string name, JarFileEntry? defaultEntry)
285 {
286 if (BASE_VERSION_FEATURE < _versionFeature && name.StartsWith(META_INF) == false)
287 {
288 // start with version of JAR file itself
289 var version = _versionFeature;
290
291 // scan each version from JAR file version down to base version
292 while (version >= BASE_VERSION_FEATURE)
293 {
294 var entry = _archive.GetEntry(META_INF_VERSIONS + version + "/" + name);
295 if (entry != null)
296 return new JarFileEntry(this, entry, name, version);
297
298 version--;
299 }
300 }
301
302 return defaultEntry;
303 }
304
308 public void Dispose()
309 {
310 if (_dispose && _archive != null)
311 _archive.Dispose();
312 }
313
314 }
315
316}
System.Threading.Interlocked Interlocked