1 // -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
4 // Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
5 // ---------------------------------------------------------------------------
6 // SquirrelJME is under the Mozilla Public License Version 2.0.
7 // See license.mkd for licensing and copyright information.
8 // ---------------------------------------------------------------------------
10 package cc
.squirreljme
.jvm
.manifest
;
12 import java
.io
.BufferedReader
;
13 import java
.io
.IOException
;
14 import java
.io
.InputStream
;
15 import java
.io
.InputStreamReader
;
16 import java
.io
.Reader
;
17 import java
.util
.AbstractMap
;
18 import java
.util
.HashMap
;
19 import java
.util
.LinkedHashMap
;
24 * This contains decoders for the standard Java manifest format.
26 * This class is immutable.
30 public final class JavaManifest
31 extends AbstractMap
<String
, JavaManifestAttributes
>
33 /** The attributes defined in this manifest file. */
34 private final Map
<String
, JavaManifestAttributes
> _attributes
;
37 * Initializes a blank manifest.
43 // Initialize a blank set of main attributes
44 Map
<String
, JavaManifestAttributes
> backing
= new HashMap
<>();
45 backing
.put("", new JavaManifestAttributes());
47 // Lock in the backing map
48 this._attributes
= backing
;
52 * Decodes the manifest from the given input stream, it is treated as
53 * UTF-8 as per the JAR specification.
55 * @param __is The input stream for the manifest data.
56 * @throws IOException On read errors.
57 * @throws JavaManifestException If the manifest is malformed.
58 * @throws NullPointerException On null arguments.
61 public JavaManifest(InputStream __is
)
62 throws IOException
, JavaManifestException
, NullPointerException
64 this(new BufferedReader(
65 new InputStreamReader(__is
, "utf-8")));
69 * Decodes the manifest from the given reader.
71 * @param __r The characters which make up the manifest.
72 * @throws IOException On read errors.
73 * @throws JavaManifestException If the manifest is malformed.
74 * @throws NullPointerException On null arguments.
77 public JavaManifest(Reader __r
)
78 throws IOException
, JavaManifestException
, NullPointerException
82 throw new NullPointerException("NARG");
84 // The backing map and temporary key/value pairs for each
87 Map
<String
, JavaManifestAttributes
> backing
= new LinkedHashMap
<>();
88 Map
<JavaManifestKey
, String
> working
= new LinkedHashMap
<>();
90 // Read input file line by line, since it is more efficient than
91 // character by character
92 StringBuilder vsb
= new StringBuilder(128);
93 BufferedReader br
= new BufferedReader(__r
);
94 for (String pln
= null;;)
97 String ln
= (pln
!= null ? pln
: br
.readLine());
102 // If the line is blank, it starts a new attribute block
105 // Store the current working set
108 backing
.put(curname
, new JavaManifestAttributes(working
));
110 // It was stored in the map, so forget it
119 // This will be a name: value line, so find the colon on it
120 /* {@squirreljme.error BB01 Expected colon to appear on the
121 manifest line, to split a name/value pair.} */
122 int col
= ln
.indexOf(':');
124 throw new JavaManifestException("BB01");
126 // Read key and value
127 String key
= ln
.substring(0, col
);
129 /* {@squirreljme.error BB02 Manifest key contains an invalid
131 for (int i
= 0, n
= key
.length(); i
< n
; i
++)
132 if (!JavaManifest
.__isKeyChar(key
.charAt(i
)))
133 throw new JavaManifestException(
134 String
.format("BB02 %s", key
));
136 // Need to skip the actual colon
139 // Skip spaces at the start of the value line
141 while (col
< n
&& ln
.charAt(col
) == ' ')
144 // Place value as it is now into a temporary buffer
145 vsb
.append(ln
, col
, n
);
147 // Read the next line to determine if it is a continuation
155 // If this is a non-continuation line, it will be a blank
156 // line or some other value, so redo the loop
157 if (!pln
.startsWith(" "))
160 // Stop at the first non-space
162 for (n
= pln
.length(); nsp
< n
; nsp
++)
163 if (pln
.charAt(nsp
) != ' ')
166 // Append split into the buffer
167 vsb
.append(pln
, nsp
, n
);
169 // Clear the line, so it is not repeated
173 // Build key and value
174 JavaManifestKey ak
= new JavaManifestKey(key
);
175 String av
= vsb
.toString();
177 // Was this the start of a new section?
180 /* {@squirreljme.error BB03 New section must start with
181 {@code Name: value}. (The input section)} */
182 if (!"name".equals(ak
.string
))
183 throw new JavaManifestException("BB03 " + ak
);
185 // The current name becomes the value
188 // Empty map to store values into
189 working
= new HashMap
<>();
192 // Otherwise, add to the map
200 // Make sure the attribute is added to the set
202 backing
.put(curname
, new JavaManifestAttributes(working
));
204 // Lock in the backing map
205 this._attributes
= backing
;
213 public boolean containsKey(Object __k
)
215 return this._attributes
.containsKey(__k
);
223 public Set
<Map
.Entry
<String
, JavaManifestAttributes
>> entrySet()
225 return this._attributes
.entrySet();
234 public JavaManifestAttributes
get(Object __k
)
236 return this._attributes
.get(__k
);
240 * Returns the mapping of main attributes.
242 * @return The main attribute mapping.
245 public JavaManifestAttributes
getMainAttributes()
247 return this._attributes
.get("");
257 return this._attributes
.size();
261 * Returns {@code true} if the specified character is an alpha-numeric
264 * @param __c The character to check.
265 * @return {@code true} if an alpha-numeric character.
268 private static boolean __isAlphaNum(char __c
)
270 return (__c
>= 'A' && __c
<= 'Z') ||
271 (__c
>= 'a' && __c
<= 'z') ||
272 (__c
>= '0' && __c
<= '9');
276 * Returns {@code true} if the character is part of a character which would
277 * be used for key values.
279 * @param __c The character to check.
280 * @return {@code true} if a key character.
283 private static boolean __isKeyChar(char __c
)
285 return JavaManifest
.__isAlphaNum(__c
) || __c
== '_' || __c
== '-';