001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.utils;
018
019import static org.apache.juneau.commons.utils.IoUtils.*;
020import static org.apache.juneau.commons.utils.ThrowableUtils.*;
021
022import java.io.*;
023import java.net.*;
024import java.nio.file.*;
025import java.util.*;
026import java.util.function.*;
027import java.util.jar.*;
028
029import org.apache.juneau.*;
030import org.apache.juneau.collections.*;
031
032/**
033 * Utility class for working with Jar manifest files.
034 *
035 * <p>
036 * Copies the contents of a {@link Manifest} into an {@link JsonMap} so that the various convenience methods on that
037 * class can be used to retrieve values.
038 *
039 *
040 * @serial exclude
041 */
042@SuppressWarnings("resource")
043public class ManifestFile extends JsonMap {
044
045   private static final long serialVersionUID = 1L;
046
047   /**
048    * Finds and loads the manifest file of the jar file that the specified class is contained within.
049    *
050    * @param c The class to get the manifest file of.
051    * @throws IOException If a problem occurred while trying to read the manifest file.
052    */
053   public ManifestFile(Class<?> c) throws IOException {
054      var className = c.getSimpleName() + ".class";
055      var classPath = c.getResource(className).toString();
056      if (! classPath.startsWith("jar")) {
057         return;
058      }
059      var manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) + "/META-INF/MANIFEST.MF";
060      try {
061         var mf = new Manifest(new URL(manifestPath).openStream());
062         load(mf);
063      } catch (MalformedURLException e) {
064         throw castException(IOException.class, e);
065      } catch (IOException e) {
066         e.printStackTrace();
067      }
068   }
069
070   /**
071    * Create an instance of this class from a manifest file on the file system.
072    *
073    * @param f The manifest file.
074    * @throws IOException If a problem occurred while trying to read the manifest file.
075    */
076   public ManifestFile(File f) throws IOException {
077      var mf = new Manifest();
078      try (var fis = new FileInputStream(f)) {
079         mf.read(fis);
080         load(mf);
081      } catch (IOException e) {
082         throw ioex(e, "Problem detected in MANIFEST.MF.  Contents below:\n{0}", read(f));
083      }
084   }
085
086   /**
087    * Create an instance of this class loaded from the contents of an input stream.
088    *
089    * <p>
090    * Note that the input must end in a newline to pick up the last line!
091    *
092    * @param is The manifest file contents.
093    * @throws IOException If a problem occurred while trying to read the manifest file.
094    */
095   public ManifestFile(InputStream is) throws IOException {
096      load(new Manifest(is));
097   }
098
099   /**
100    * Create an instance of this class from a {@link Manifest} object.
101    *
102    * @param f The manifest to read from.
103    */
104   public ManifestFile(Manifest f) {
105      load(f);
106   }
107
108   /**
109    * Create an instance of this class from a manifest path on the file system.
110    *
111    * @param path The manifest path.
112    * @throws IOException If a problem occurred while trying to read the manifest path.
113    */
114   public ManifestFile(Path path) throws IOException {
115      var mf = new Manifest();
116      try (var fis = Files.newInputStream(path)) {
117         mf.read(fis);
118         load(mf);
119      } catch (IOException e) {
120         throw ioex(e, "Problem detected in MANIFEST.MF.  Contents below:\n{0}", read(path), e);
121      }
122   }
123
124   /**
125    * Create an instance of this class loaded from the contents of a reader.
126    *
127    * <p>
128    * Note that the input must end in a newline to pick up the last line!
129    *
130    * @param r The manifest file contents.
131    * @throws IOException If a problem occurred while trying to read the manifest file.
132    */
133   public ManifestFile(Reader r) throws IOException {
134      load(new Manifest(new ByteArrayInputStream(read(r).getBytes(UTF8))));
135   }
136
137   @Override /* Overridden from JsonMap */
138   public ManifestFile append(Map<String,Object> values) {
139      super.append(values);
140      return this;
141   }
142
143   @Override /* Overridden from JsonMap */
144   public ManifestFile append(String key, Object value) {
145      super.append(key, value);
146      return this;
147   }
148
149   @Override /* Overridden from JsonMap */
150   public ManifestFile appendIf(boolean flag, String key, Object value) {
151      super.appendIf(flag, key, value);
152      return this;
153   }
154
155   @Override /* Overridden from JsonMap */
156   public ManifestFile filtered(Predicate<Object> value) {
157      super.filtered(value);
158      return this;
159   }
160
161   @Override /* Overridden from JsonMap */
162   public ManifestFile inner(Map<String,Object> inner) {
163      super.inner(inner);
164      return this;
165   }
166
167   @Override /* Overridden from JsonMap */
168   public ManifestFile keepAll(String...keys) {
169      super.keepAll(keys);
170      return this;
171   }
172
173   @Override /* Overridden from JsonMap */
174   public ManifestFile modifiable() {
175      return this;
176   }
177
178   @Override /* Overridden from JsonMap */
179   public ManifestFile session(BeanSession session) {
180      super.session(session);
181      return this;
182   }
183
184   @Override /* Overridden from JsonMap */
185   public ManifestFile setBeanSession(BeanSession value) {
186      super.setBeanSession(value);
187      return this;
188   }
189
190   @Override /* Overridden from Object */
191   public String toString() {
192      var sb = new StringBuilder();
193      forEach((k, v) -> sb.append(k).append(": ").append(v).append("\n"));
194      return sb.toString();
195   }
196
197   @Override /* Overridden from JsonMap */
198   public ManifestFile unmodifiable() {
199      return this;
200   }
201
202   private void load(Manifest mf) {
203      mf.getMainAttributes().forEach((k, v) -> put(k.toString(), v.toString()));
204   }
205}