1use std::{io, vec};
5use std::collections::{vec_deque, VecDeque};
6use std::convert::Infallible;
7use std::ffi::{OsStr, OsString};
8use std::fmt::{Display, Formatter, Write};
9use std::fs::File;
10use std::io::{ErrorKind, Read};
11use std::ops::{Add, AddAssign};
12use std::path::{Path, PathBuf};
13use std::str::FromStr;
14
15use cfg_if::cfg_if;
16use static_assertions::assert_impl_all;
17use url::Url;
18use zip::result::ZipError;
19use zip::ZipArchive;
20
21cfg_if! {
22 if #[cfg(windows)] {
23 pub const CLASSPATH_SEPARATOR: char = ';';
26 } else if #[cfg(unix)] {
27 pub const CLASSPATH_SEPARATOR: char = ':';
30 }
31}
32
33#[derive(Debug, PartialEq, Eq, Hash, Clone, Default)]
35pub struct Classpath {
36 paths: VecDeque<PathBuf>,
37}
38
39impl Classpath {
40 pub fn new() -> Self {
42 Default::default()
43 }
44
45 pub fn is_empty(&self) -> bool {
47 self.paths.is_empty()
48 }
49
50 pub fn len(&self) -> usize {
52 self.paths.len()
53 }
54
55 pub fn as_os_string(&self) -> OsString {
65 self.paths.iter().fold(OsString::new(), |mut accum, path| {
66 if accum.is_empty() {
67 path.clone().into_os_string()
68 } else {
69 accum
70 .write_char(CLASSPATH_SEPARATOR)
71 .expect("couldn't add separator");
72 accum.push(path);
73 accum
74 }
75 })
76 }
77
78 pub fn get<P: AsRef<str>>(&self, path: P) -> Option<io::Result<Resource>> {
94 let stripped = path.as_ref().trim_start_matches("/");
95 for entry in self {
96 if entry.is_dir() {
97 if let Some(ret) = Self::get_in_dir(entry, stripped) {
98 return Some(ret);
99 }
100 } else {
101 let ext = entry.extension();
102 match ext.and_then(|os| os.to_str()) {
103 Some("jar") | Some("zip") => {
104 match Self::get_in_archive(
105 entry, stripped
106 ) {
107 Ok(Some(resource)) => {
108 return Some(Ok(resource))
109 }
110 Ok(None) => { }
111 Err(e) => {
112 return Some(Err(e))
113 }
114 }
115 }
116 _ => { }
117 }
118 }
119 }
120
121 None
122 }
123
124 fn get_in_archive(archive_path: &Path, entry_path: &str) -> io::Result<Option<Resource>> {
125 let archive_file = File::open(archive_path)?;
126 let mut archive = ZipArchive::new(archive_file)
127 .map_err(|e| io::Error::new(ErrorKind::InvalidData, e.to_string()))?;
128
129 let out = match archive.by_name(entry_path) {
130 Ok(mut entry) => {
131 let mut buffer = vec![];
132 entry.read_to_end(&mut buffer)?;
133 Ok(Some(
134 Resource {
135 kind: ResourceKind::ArchiveEntry(VecDeque::from(buffer)),
136 url: Url::parse(
137 &format!("jar:file:{archive}!{entry_path}", archive = archive_path.to_str().unwrap())
138 ).unwrap()
139 }
140 ))
141 }
142 Err(err) => {
143 match err {
144 ZipError::FileNotFound => {
145 Ok(None)
146 }
147 e => {
148 Err(io::Error::new(ErrorKind::InvalidData, e))
149 }
150 }
151 }
152 };
153 out
154 }
155
156 fn get_in_dir(dir: &Path, entry: &str) -> Option<io::Result<Resource>> {
157 let full_path = dir.join(entry);
158 if full_path.exists() {
159 Some(
160 File::open(&full_path)
161 .and_then(|file| {
162 Url::from_file_path(&full_path)
163 .map_err(|()| {
164 io::Error::new(
165 io::ErrorKind::NotFound,
166 format!("{:?} is not valid as a url", full_path),
167 )
168 })
169 .map(|url| (file, url))
170 })
171 .map(|(file, url)| Resource {
172 kind: ResourceKind::Real(file),
173 url,
174 }),
175 )
176 } else {
177 None
178 }
179 }
180}
181
182impl Classpath {
184 pub fn push_front<P: AsRef<Path>>(&mut self, path: P) {
186 self.paths.push_front(path.as_ref().to_path_buf());
187 }
188
189 pub fn push_back<P: AsRef<Path>>(&mut self, path: P) {
191 self.paths.push_back(path.as_ref().to_path_buf());
192 }
193
194 pub fn join(self, other: Self) -> Self {
197 let mut paths = self.paths;
198 paths.extend(other.paths);
199 Self { paths }
200 }
201}
202
203impl Display for Classpath {
204 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
205 write!(f, "{:?}", self.as_os_string())
206 }
207}
208
209impl<P> FromIterator<P> for Classpath
210where
211 P: AsRef<Path>,
212{
213 fn from_iter<T: IntoIterator<Item = P>>(iter: T) -> Self {
214 Self {
215 paths: iter.into_iter().map(|p| p.as_ref().to_path_buf()).collect(),
216 }
217 }
218}
219
220impl From<Vec<PathBuf>> for Classpath {
221 fn from(vec: Vec<PathBuf>) -> Self {
222 Self::from_iter(vec)
223 }
224}
225
226impl From<&Path> for Classpath {
227 fn from(path: &Path) -> Self {
229 let mut output = Self::new();
230 output.push_front(path);
231 output
232 }
233}
234
235impl From<PathBuf> for Classpath {
236 fn from(path: PathBuf) -> Self {
238 let mut output = Self::new();
239 output.push_front(path);
240 output
241 }
242}
243
244impl From<&str> for Classpath {
245 fn from(path: &str) -> Self {
247 let mut output = Self::new();
248 output.push_front(path);
249 output
250 }
251}
252
253impl From<&OsStr> for Classpath {
254 fn from(path: &OsStr) -> Self {
256 let mut output = Self::new();
257 output.push_front(path);
258 output
259 }
260}
261
262impl FromStr for Classpath {
263 type Err = Infallible;
264
265 fn from_str(s: &str) -> Result<Self, Self::Err> {
267 Ok(Self::from_iter(s.split(CLASSPATH_SEPARATOR)))
268 }
269}
270
271impl IntoIterator for Classpath {
272 type Item = PathBuf;
273 type IntoIter = vec_deque::IntoIter<PathBuf>;
274
275 fn into_iter(self) -> Self::IntoIter {
276 self.paths.into_iter()
277 }
278}
279
280impl<'a> IntoIterator for &'a Classpath {
281 type Item = &'a Path;
282 type IntoIter = vec::IntoIter<&'a Path>;
283
284 fn into_iter(self) -> Self::IntoIter {
285 self.paths
286 .iter()
287 .map(|s| s.as_path())
288 .collect::<Vec<_>>()
289 .into_iter()
290 }
291}
292
293impl<P: AsRef<Path>> Extend<P> for Classpath {
294 fn extend<T: IntoIterator<Item = P>>(&mut self, iter: T) {
295 self.paths
296 .extend(iter.into_iter().map(|p| p.as_ref().to_path_buf()))
297 }
298}
299
300impl Add for Classpath {
301 type Output = Self;
302
303 fn add(self, rhs: Self) -> Self::Output {
304 self.join(rhs)
305 }
306}
307
308impl AddAssign for Classpath {
309 fn add_assign(&mut self, rhs: Self) {
310 self.extend(rhs)
311 }
312}
313
314
315
316#[derive(Debug)]
318pub struct Resource {
319 kind: ResourceKind,
320 url: Url,
321}
322
323impl Resource {
324 pub fn url(&self) -> &Url {
326 &self.url
327 }
328}
329
330assert_impl_all!(Resource: io::Read);
331
332impl io::Read for Resource {
333 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
334 self.kind.read(buf)
335 }
336}
337
338#[derive(Debug)]
339enum ResourceKind {
340 Real(File),
341 ArchiveEntry(VecDeque<u8>),
342}
343
344assert_impl_all!(ResourceKind: io::Read);
345
346impl io::Read for ResourceKind {
347 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
348 match self {
349 ResourceKind::Real(file) => file.read(buf),
350 ResourceKind::ArchiveEntry(old_buf) => {
351 old_buf.read(buf)
352 }
353 }
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use std::ffi::OsString;
360
361 use crate::{Classpath, CLASSPATH_SEPARATOR};
362
363 #[test]
364 fn as_path() {
365 let cp = Classpath::from_iter(["path1", "path2"]);
366 let as_path = cp.as_os_string();
367 assert_eq!(
368 as_path,
369 OsString::from(format!("path1{}path2", CLASSPATH_SEPARATOR))
370 );
371 }
372
373 #[test]
374 fn join() {
375 let cp1 = Classpath::from("path1");
376 let cp2 = Classpath::from("path2");
377 assert_eq!(cp1.join(cp2), Classpath::from_iter(["path1", "path2"]));
378 }
379
380 #[test]
381 fn add_classpaths() {
382 let mut cp = Classpath::new();
383 cp = cp + Classpath::from("path1");
384 cp += Classpath::from_iter(["path2", "path3"]);
385 assert_eq!(cp, Classpath::from_iter(["path1", "path2", "path3"]));
386 }
387
388 #[test]
389 fn from_string() {
390 let classpath: Classpath = format!("path1{}path2", CLASSPATH_SEPARATOR)
391 .parse()
392 .unwrap();
393 assert_eq!(classpath, Classpath::from_iter(["path1", "path2"]))
394 }
395}