1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597
|
/*******************************************************************************
* Copyright (c) 2003, 2010 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.osgi.util;
import java.io.*;
import java.util.*;
import org.eclipse.osgi.framework.debug.Debug;
import org.eclipse.osgi.framework.internal.core.Msg;
import org.eclipse.osgi.framework.internal.core.Tokenizer;
import org.osgi.framework.BundleException;
/**
* This class represents a single manifest element. A manifest element must consist of a single
* {@link String} value. The {@link String} value may be split up into component values each
* separated by a semi-colon (';'). A manifest element may optionally have a set of
* attribute and directive values associated with it. The general syntax of a manifest element is as follows:
* <p>
* <pre>
* ManifestElement ::= component (';' component)* (';' parameter)*
* component ::= ([^;,:="\#x0D#x0A#x00])+ | quoted-string
* quoted-string::= '"' ( [^"\#x0D#x0A#x00] | '\"'| '\\')* '"'
* parameter ::= directive | attribute
* directive ::= token ':=' argument
* attribute ::= token '=' argument
* argument ::= extended | quoted-string
* token ::= ( alphanum | '_' | '-' )+
* extended ::= ( alphanum | '_' | '-' | '.' )+
* </pre>
* </p>
* <p>
* For example, the following is an example of a manifest element to the <tt>Export-Package</tt> header:
* </p>
* <p>
* <pre>
* org.osgi.framework; specification-version="1.2"; another-attr="examplevalue"
* </pre>
* </p>
* <p>
* This manifest element has a value of <tt>org.osgi.framework</tt> and it has two attributes,
* <tt>specification-version</tt> and <tt>another-attr</tt>.
* </p>
* <p>
* The following manifest element is an example of a manifest element that has multiple
* components to its value:
* </p>
* <p>
* <pre>
* code1.jar;code2.jar;code3.jar;attr1=value1;attr2=value2;attr3=value3
* </pre>
* </p>
* <p>
* This manifest element has a value of <tt>code1.jar;code2.jar;code3.jar</tt>.
* This is an example of a multiple component value. This value has three
* components: <tt>code1.jar</tt>, <tt>code2.jar</tt>, and <tt>code3.jar</tt>.
* </p>
* <p>
* If components contain delimiter characters (e.g ';', ',' ':' "=") then it must be
* a quoted string. For example, the following is an example of a manifest element
* that has multiple components containing delimiter characters:
* </p>
* <pre>
* "component ; 1"; "component , 2"; "component : 3"; attr1=value1; attr2=value2; attr3=value3
* </pre>
* <p>
* This manifest element has a value of <tt>"component ; 1"; "component , 2"; "component : 3"</tt>.
* This value has three components: <tt>"component ; 1"</tt>, <tt>"component , 2"</tt>, <tt>"component : 3"</tt>.
* </p>
* <p>
* This class is not intended to be subclassed by clients.
* </p>
*
* @since 3.0
* @noextend This class is not intended to be subclassed by clients.
*/
public class ManifestElement {
/**
* The value of the manifest element.
*/
private final String mainValue;
/**
* The value components of the manifest element.
*/
private final String[] valueComponents;
/**
* The table of attributes for the manifest element.
*/
private Hashtable<String, Object> attributes;
/**
* The table of directives for the manifest element.
*/
private Hashtable<String, Object> directives;
/**
* Constructs an empty manifest element with no value or attributes.
*/
private ManifestElement(String value, String[] valueComponents) {
this.mainValue = value;
this.valueComponents = valueComponents;
}
/**
* Returns the value of the manifest element. The value returned is the
* complete value up to the first attribute or directive. For example, the
* following manifest element:
* <p>
* <pre>
* test1.jar;test2.jar;test3.jar;selection-filter="(os.name=Windows XP)"
* </pre>
* </p>
* <p>
* This manifest element has a value of <tt>test1.jar;test2.jar;test3.jar</tt>
* </p>
*
* @return the value of the manifest element.
*/
public String getValue() {
return mainValue;
}
/**
* Returns the value components of the manifest element. The value
* components returned are the complete list of value components up to
* the first attribute or directive.
* For example, the following manifest element:
* <p>
* <pre>
* test1.jar;test2.jar;test3.jar;selection-filter="(os.name=Windows XP)"
* </pre>
* </p>
* <p>
* This manifest element has the value components array
* <tt>{ "test1.jar", "test2.jar", "test3.jar" }</tt>
* Each value component is delemited by a semi-colon (<tt>';'</tt>).
* </p>
*
* @return the String[] of value components
*/
public String[] getValueComponents() {
return valueComponents;
}
/**
* Returns the value for the specified attribute or <code>null</code> if it does
* not exist. If the attribute has multiple values specified then the last value
* specified is returned. For example the following manifest element:
* <p>
* <pre>
* elementvalue; myattr="value1"; myattr="value2"
* </pre>
* </p>
* <p>
* specifies two values for the attribute key <tt>myattr</tt>. In this case <tt>value2</tt>
* will be returned because it is the last value specified for the attribute
* <tt>myattr</tt>.
* </p>
*
* @param key the attribute key to return the value for
* @return the attribute value or <code>null</code>
*/
public String getAttribute(String key) {
return getTableValue(attributes, key);
}
/**
* Returns an array of values for the specified attribute or
* <code>null</code> if the attribute does not exist.
*
* @param key the attribute key to return the values for
* @return the array of attribute values or <code>null</code>
* @see #getAttribute(String)
*/
public String[] getAttributes(String key) {
return getTableValues(attributes, key);
}
/**
* Returns an enumeration of attribute keys for this manifest element or
* <code>null</code> if none exist.
*
* @return the enumeration of attribute keys or null if none exist.
*/
public Enumeration<String> getKeys() {
return getTableKeys(attributes);
}
/**
* Add an attribute to this manifest element.
*
* @param key the key of the attribute
* @param value the value of the attribute
*/
private void addAttribute(String key, String value) {
attributes = addTableValue(attributes, key, value);
}
/**
* Returns the value for the specified directive or <code>null</code> if it
* does not exist. If the directive has multiple values specified then the
* last value specified is returned. For example the following manifest element:
* <p>
* <pre>
* elementvalue; mydir:="value1"; mydir:="value2"
* </pre>
* </p>
* <p>
* specifies two values for the directive key <tt>mydir</tt>. In this case <tt>value2</tt>
* will be returned because it is the last value specified for the directive <tt>mydir</tt>.
* </p>
*
* @param key the directive key to return the value for
* @return the directive value or <code>null</code>
*/
public String getDirective(String key) {
return getTableValue(directives, key);
}
/**
* Returns an array of string values for the specified directives or
* <code>null</code> if it does not exist.
*
* @param key the directive key to return the values for
* @return the array of directive values or <code>null</code>
* @see #getDirective(String)
*/
public String[] getDirectives(String key) {
return getTableValues(directives, key);
}
/**
* Return an enumeration of directive keys for this manifest element or
* <code>null</code> if there are none.
*
* @return the enumeration of directive keys or <code>null</code>
*/
public Enumeration<String> getDirectiveKeys() {
return getTableKeys(directives);
}
/**
* Add a directive to this manifest element.
*
* @param key the key of the attribute
* @param value the value of the attribute
*/
private void addDirective(String key, String value) {
directives = addTableValue(directives, key, value);
}
/*
* Return the last value associated with the given key in the specified table.
*/
private String getTableValue(Hashtable<String, Object> table, String key) {
if (table == null)
return null;
Object result = table.get(key);
if (result == null)
return null;
if (result instanceof String)
return (String) result;
@SuppressWarnings("unchecked")
List<String> valueList = (List<String>) result;
//return the last value
return valueList.get(valueList.size() - 1);
}
/*
* Return the values associated with the given key in the specified table.
*/
private String[] getTableValues(Hashtable<String, Object> table, String key) {
if (table == null)
return null;
Object result = table.get(key);
if (result == null)
return null;
if (result instanceof String)
return new String[] {(String) result};
@SuppressWarnings("unchecked")
List<String> valueList = (List<String>) result;
return valueList.toArray(new String[valueList.size()]);
}
/*
* Return an enumeration of table keys for the specified table.
*/
private Enumeration<String> getTableKeys(Hashtable<String, Object> table) {
if (table == null)
return null;
return table.keys();
}
/*
* Add the given key/value association to the specified table. If an entry already exists
* for this key, then create an array list from the current value (if necessary) and
* append the new value to the end of the list.
*/
@SuppressWarnings("unchecked")
private Hashtable<String, Object> addTableValue(Hashtable<String, Object> table, String key, String value) {
if (table == null) {
table = new Hashtable<String, Object>(7);
}
Object curValue = table.get(key);
if (curValue != null) {
List<String> newList;
// create a list to contain multiple values
if (curValue instanceof List) {
newList = (List<String>) curValue;
} else {
newList = new ArrayList<String>(5);
newList.add((String) curValue);
}
newList.add(value);
table.put(key, newList);
} else {
table.put(key, value);
}
return table;
}
/**
* Parses a manifest header value into an array of ManifestElements. Each
* ManifestElement returned will have a non-null value returned by getValue().
*
* @param header the header name to parse. This is only specified to provide error messages
* when the header value is invalid.
* @param value the header value to parse.
* @return the array of ManifestElements that are represented by the header value; null will be
* returned if the value specified is null or if the value does not parse into
* one or more ManifestElements.
* @throws BundleException if the header value is invalid
*/
public static ManifestElement[] parseHeader(String header, String value) throws BundleException {
if (value == null)
return (null);
List<ManifestElement> headerElements = new ArrayList<ManifestElement>(10);
Tokenizer tokenizer = new Tokenizer(value);
parseloop: while (true) {
String next = tokenizer.getString(";,"); //$NON-NLS-1$
if (next == null)
throw new BundleException(NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, header, value), BundleException.MANIFEST_ERROR);
List<String> headerValues = new ArrayList<String>();
StringBuffer headerValue = new StringBuffer(next);
headerValues.add(next);
if (Debug.DEBUG_MANIFEST)
Debug.print("parseHeader: " + next); //$NON-NLS-1$
boolean directive = false;
char c = tokenizer.getChar();
// Header values may be a list of ';' separated values. Just append them all into one value until the first '=' or ','
while (c == ';') {
next = tokenizer.getString(";,=:"); //$NON-NLS-1$
if (next == null)
throw new BundleException(NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, header, value), BundleException.MANIFEST_ERROR);
c = tokenizer.getChar();
while (c == ':') { // may not really be a :=
c = tokenizer.getChar();
if (c != '=') {
String restOfNext = tokenizer.getToken(";,=:"); //$NON-NLS-1$
if (restOfNext == null)
throw new BundleException(NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, header, value), BundleException.MANIFEST_ERROR);
next += ":" + c + restOfNext; //$NON-NLS-1$
c = tokenizer.getChar();
} else
directive = true;
}
if (c == ';' || c == ',' || c == '\0') /* more */{
headerValues.add(next);
headerValue.append(";").append(next); //$NON-NLS-1$
if (Debug.DEBUG_MANIFEST)
Debug.print(";" + next); //$NON-NLS-1$
}
}
// found the header value create a manifestElement for it.
ManifestElement manifestElement = new ManifestElement(headerValue.toString(), headerValues.toArray(new String[headerValues.size()]));
// now add any attributes/directives for the manifestElement.
while (c == '=' || c == ':') {
while (c == ':') { // may not really be a :=
c = tokenizer.getChar();
if (c != '=') {
String restOfNext = tokenizer.getToken("=:"); //$NON-NLS-1$
if (restOfNext == null)
throw new BundleException(NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, header, value), BundleException.MANIFEST_ERROR);
next += ":" + c + restOfNext; //$NON-NLS-1$
c = tokenizer.getChar();
} else
directive = true;
}
// determine if the attribute is the form attr:List<type>
String preserveEscapes = null;
if (!directive && next.indexOf("List") > 0) { //$NON-NLS-1$
Tokenizer listTokenizer = new Tokenizer(next);
String attrKey = listTokenizer.getToken(":"); //$NON-NLS-1$
if (attrKey != null && listTokenizer.getChar() == ':' && "List".equals(listTokenizer.getToken("<"))) { //$NON-NLS-1$//$NON-NLS-2$
// we assume we must preserve escapes for , and "
preserveEscapes = "\\,"; //$NON-NLS-1$
}
}
String val = tokenizer.getString(";,", preserveEscapes); //$NON-NLS-1$
if (val == null)
throw new BundleException(NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, header, value), BundleException.MANIFEST_ERROR);
if (Debug.DEBUG_MANIFEST)
Debug.print(";" + next + "=" + val); //$NON-NLS-1$ //$NON-NLS-2$
try {
if (directive)
manifestElement.addDirective(next, val);
else
manifestElement.addAttribute(next, val);
directive = false;
} catch (Exception e) {
throw new BundleException(NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, header, value), BundleException.MANIFEST_ERROR, e);
}
c = tokenizer.getChar();
if (c == ';') /* more */{
next = tokenizer.getToken("=:"); //$NON-NLS-1$
if (next == null)
throw new BundleException(NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, header, value), BundleException.MANIFEST_ERROR);
c = tokenizer.getChar();
}
}
headerElements.add(manifestElement);
if (Debug.DEBUG_MANIFEST)
Debug.println(""); //$NON-NLS-1$
if (c == ',') /* another manifest element */
continue parseloop;
if (c == '\0') /* end of value */
break parseloop;
throw new BundleException(NLS.bind(Msg.MANIFEST_INVALID_HEADER_EXCEPTION, header, value), BundleException.MANIFEST_ERROR);
}
int size = headerElements.size();
if (size == 0)
return (null);
ManifestElement[] result = headerElements.toArray(new ManifestElement[size]);
return (result);
}
/**
* Returns the result of converting a list of comma-separated tokens into an array.
*
* @return the array of string tokens or <code>null</code> if there are none
* @param stringList the initial comma-separated string
*/
public static String[] getArrayFromList(String stringList) {
String[] result = getArrayFromList(stringList, ","); //$NON-NLS-1$
return result.length == 0 ? null : result;
}
/**
* Returns the result of converting a list of tokens into an array. The tokens
* are split using the specified separator.
*
* @return the array of string tokens. If there are none then an empty array
* is returned.
* @param stringList the initial string list
* @param separator the separator to use to split the list into tokens.
* @since 3.2
*/
public static String[] getArrayFromList(String stringList, String separator) {
if (stringList == null || stringList.trim().length() == 0)
return new String[0];
List<String> list = new ArrayList<String>();
StringTokenizer tokens = new StringTokenizer(stringList, separator);
while (tokens.hasMoreTokens()) {
String token = tokens.nextToken().trim();
if (token.length() != 0)
list.add(token);
}
return list.toArray(new String[list.size()]);
}
/**
* Parses a bundle manifest and puts the header/value pairs into the supplied Map.
* Only the main section of the manifest is parsed (up to the first blank line). All
* other sections are ignored. If a header is duplicated then only the last
* value is stored in the map.
* <p>
* The supplied input stream is consumed by this method and will be closed.
* If the supplied Map is null then a Map is created to put the header/value pairs into.
* </p>
* @param manifest an input stream for a bundle manifest.
* @param headers a map used to put the header/value pairs from the bundle manifest. This value may be null.
* @throws BundleException if the manifest has an invalid syntax
* @throws IOException if an error occurs while reading the manifest
* @return the map with the header/value pairs from the bundle manifest
*/
public static Map<String, String> parseBundleManifest(InputStream manifest, Map<String, String> headers) throws IOException, BundleException {
if (headers == null)
headers = new HashMap<String, String>();
BufferedReader br;
try {
br = new BufferedReader(new InputStreamReader(manifest, "UTF8")); //$NON-NLS-1$
} catch (UnsupportedEncodingException e) {
br = new BufferedReader(new InputStreamReader(manifest));
}
try {
String header = null;
StringBuffer value = new StringBuffer(256);
boolean firstLine = true;
while (true) {
String line = br.readLine();
/* The java.util.jar classes in JDK 1.3 use the value of the last
* encountered manifest header. So we do the same to emulate
* this behavior. We no longer throw a BundleException
* for duplicate manifest headers.
*/
if ((line == null) || (line.length() == 0)) /* EOF or empty line */
{
if (!firstLine) /* flush last line */
{
headers.put(header, value.toString().trim());
}
break; /* done processing main attributes */
}
if (line.charAt(0) == ' ') /* continuation */
{
if (firstLine) /* if no previous line */
{
throw new BundleException(NLS.bind(Msg.MANIFEST_INVALID_SPACE, line), BundleException.MANIFEST_ERROR);
}
value.append(line.substring(1));
continue;
}
if (!firstLine) {
headers.put(header, value.toString().trim());
value.setLength(0); /* clear StringBuffer */
}
int colon = line.indexOf(':');
if (colon == -1) /* no colon */
{
throw new BundleException(NLS.bind(Msg.MANIFEST_INVALID_LINE_NOCOLON, line), BundleException.MANIFEST_ERROR);
}
header = line.substring(0, colon).trim();
value.append(line.substring(colon + 1));
firstLine = false;
}
} finally {
try {
br.close();
} catch (IOException ee) {
// do nothing
}
}
return headers;
}
public String toString() {
Enumeration<String> attrKeys = getKeys();
Enumeration<String> directiveKeys = getDirectiveKeys();
if (attrKeys == null && directiveKeys == null)
return mainValue;
StringBuffer result = new StringBuffer(mainValue);
if (attrKeys != null) {
while (attrKeys.hasMoreElements()) {
String key = attrKeys.nextElement();
addValues(false, key, getAttributes(key), result);
}
}
if (directiveKeys != null) {
while (directiveKeys.hasMoreElements()) {
String key = directiveKeys.nextElement();
addValues(true, key, getDirectives(key), result);
}
}
return result.toString();
}
private void addValues(boolean directive, String key, String[] values, StringBuffer result) {
if (values == null)
return;
for (int i = 0; i < values.length; i++) {
result.append(';').append(key);
if (directive)
result.append(':');
result.append("=\"").append(values[i]).append('\"'); //$NON-NLS-1$
}
}
}
|