| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.sling.commons.osgi; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * This is a helper class to parse manifest header entries. |
| */ |
| public class ManifestHeader { |
| |
| /** |
| * A header can have several entries separated by comma. |
| */ |
| public interface Entry { |
| |
| /** |
| * @return The value of the entry. |
| */ |
| String getValue(); |
| |
| /** |
| * @return The attributes specified for this entry. |
| */ |
| NameValuePair[] getAttributes(); |
| |
| /** |
| * @return The directives for this entry. |
| */ |
| NameValuePair[] getDirectives(); |
| |
| String getAttributeValue(String name); |
| |
| String getDirectiveValue(String name); |
| } |
| |
| /** The entries for this header. */ |
| private Entry[] entries = new Entry[0]; |
| |
| /** |
| * Add new entries from parsing. |
| */ |
| private void add(Entry[] paths) { |
| if ( paths != null && paths.length > 0 ) { |
| final Entry[] copy = new Entry[this.entries.length + paths.length]; |
| System.arraycopy(this.entries, 0, copy, 0, this.entries.length); |
| System.arraycopy(paths, 0, copy, this.entries.length, paths.length); |
| this.entries = copy; |
| } |
| } |
| |
| /** |
| * @return Return the entries for this header. |
| */ |
| public Entry[] getEntries() { |
| return this.entries; |
| } |
| |
| /** |
| * Directives and attributes are simple name/value pairs. |
| */ |
| public final static class NameValuePair { |
| |
| private final String name; |
| private final String value; |
| |
| public NameValuePair(String name, String value) { |
| this.name = name; |
| this.value = value; |
| } |
| |
| public String getName() { |
| return name; |
| } |
| |
| public String getValue() { |
| return value; |
| } |
| } |
| |
| private static final String CLASS_PATH_SEPARATOR = ","; |
| private static final String PACKAGE_SEPARATOR = ";"; |
| private static final String DIRECTIVE_SEPARATOR = ":="; |
| private static final String ATTRIBUTE_SEPARATOR = "="; |
| |
| /** |
| * Parse headers |
| * Like this: path; path; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2, |
| * path; path; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2 |
| * The returned object maintains the order of entries (paths), directives and attributes. |
| * @param header Header name |
| * @return Parsed header or null if not found |
| */ |
| public static ManifestHeader parse(String header) { |
| final ManifestHeader entry = new ManifestHeader(); |
| |
| if (header != null) { |
| if (header.length() == 0) { |
| throw new IllegalArgumentException("A header cannot be an empty string."); |
| } |
| |
| final String[] clauseStrings = parseDelimitedString(header, CLASS_PATH_SEPARATOR); |
| if ( clauseStrings != null ) { |
| for(final String clause : clauseStrings) { |
| entry.add(parseStandardHeaderClause(clause)); |
| } |
| } |
| } |
| |
| return (entry.getEntries().length == 0) ? null : entry; |
| } |
| |
| /** |
| * Parse a clause |
| * Like this: path; path; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2 |
| */ |
| private static ManifestHeader.Entry[] parseStandardHeaderClause(String clauseString) |
| throws IllegalArgumentException { |
| // Break string into semi-colon delimited pieces. |
| String[] pieces = parseDelimitedString(clauseString, PACKAGE_SEPARATOR); |
| |
| // Count the number of different paths; paths |
| // will not have an '=' in their string. This assumes |
| // that paths come first, before directives and |
| // attributes. |
| int pathCount = 0; |
| for (int pieceIdx = 0; pieceIdx < pieces.length; pieceIdx++) { |
| if (pieces[pieceIdx].indexOf('=') >= 0) { |
| break; |
| } |
| pathCount++; |
| } |
| |
| // Error if no paths were specified. |
| if (pathCount == 0) { |
| throw new IllegalArgumentException( |
| "No paths specified in header: " + clauseString); |
| } |
| |
| // Create an array of paths. |
| PathImpl[] paths = new PathImpl[pathCount]; |
| for(int i=0;i<pathCount;i++) { |
| paths[i] = new PathImpl(pieces[i]); |
| } |
| |
| // Parse the directives/attributes |
| // and keep the order |
| // for simpliefied checking if a directive/attribute is used twice, we keep |
| // two collections: one for the values and one for the names |
| final List<ManifestHeader.NameValuePair> dirsList = new ArrayList<ManifestHeader.NameValuePair>(); |
| final Set<String> dirsNames = new HashSet<String>(); |
| final List<ManifestHeader.NameValuePair> attrsList = new ArrayList<ManifestHeader.NameValuePair>(); |
| final Set<String> attrsNames = new HashSet<String>(); |
| |
| int idx = -1; |
| String sep = null; |
| for (int pieceIdx = pathCount; pieceIdx < pieces.length; pieceIdx++) { |
| |
| if ((idx = pieces[pieceIdx].indexOf(DIRECTIVE_SEPARATOR)) >= 0) { |
| sep = DIRECTIVE_SEPARATOR; |
| } else if ((idx = pieces[pieceIdx].indexOf(ATTRIBUTE_SEPARATOR)) >= 0) { |
| sep = ATTRIBUTE_SEPARATOR; |
| } else { |
| throw new IllegalArgumentException("Not a directive/attribute: " + clauseString); |
| } |
| |
| final String key = pieces[pieceIdx].substring(0, idx).trim(); |
| String value = pieces[pieceIdx].substring(idx + sep.length()).trim(); |
| |
| // Remove quotes, if value is quoted. |
| if (value.startsWith("\"") && value.endsWith("\"")) { |
| value = value.substring(1, value.length() - 1); |
| } |
| |
| // Save the directive/attribute in the appropriate array. |
| if (sep.equals(DIRECTIVE_SEPARATOR)) { |
| // Check for duplicates. |
| if (dirsNames.contains(key)) { |
| throw new IllegalArgumentException("Duplicate directive: " + key); |
| } |
| dirsList.add(new ManifestHeader.NameValuePair(key, value)); |
| dirsNames.add(key); |
| } else { |
| // Check for duplicates. |
| if (attrsNames.contains(key)) { |
| throw new IllegalArgumentException("Duplicate attribute: " + key); |
| } |
| attrsList.add(new ManifestHeader.NameValuePair(key, value)); |
| attrsNames.add(key); |
| } |
| } |
| // Create directive array. |
| ManifestHeader.NameValuePair[] dirs = |
| dirsList.toArray(new ManifestHeader.NameValuePair[dirsList.size()]); |
| |
| // Create attribute array. |
| ManifestHeader.NameValuePair[] attrs = |
| attrsList.toArray(new ManifestHeader.NameValuePair[attrsList.size()]); |
| |
| // now set attributes and directives for each path |
| for(int i=0;i<pathCount;i++) { |
| paths[i].init(dirs, attrs); |
| } |
| |
| return paths; |
| } |
| |
| private static final int CHAR = 1; |
| private static final int DELIMITER = 2; |
| private static final int STARTQUOTE = 4; |
| private static final int ENDQUOTE = 8; |
| |
| /** |
| * Parses delimited string and returns an array containing the tokens. This |
| * parser obeys quotes, so the delimiter character will be ignored if it is |
| * inside of a quote. This method assumes that the quote character is not |
| * included in the set of delimiter characters. |
| * @param value the delimited string to parse. |
| * @param delim the characters delimiting the tokens. |
| * @return an array of string tokens or null if there were no tokens. |
| **/ |
| private static String[] parseDelimitedString(String value, String delim) { |
| if (value == null) { |
| value = ""; |
| } |
| |
| final List<String> list = new ArrayList<String>(); |
| |
| final StringBuilder sb = new StringBuilder(); |
| |
| int expecting = (CHAR | DELIMITER | STARTQUOTE); |
| |
| for (int i = 0; i < value.length(); i++) { |
| char c = value.charAt(i); |
| |
| boolean isDelimiter = (delim.indexOf(c) >= 0); |
| boolean isQuote = (c == '"'); |
| |
| if (isDelimiter && ((expecting & DELIMITER) > 0)) { |
| list.add(sb.toString().trim()); |
| sb.delete(0, sb.length()); |
| expecting = (CHAR | DELIMITER | STARTQUOTE); |
| } else if (isQuote && ((expecting & STARTQUOTE) > 0)) { |
| sb.append(c); |
| expecting = CHAR | ENDQUOTE; |
| } else if (isQuote && ((expecting & ENDQUOTE) > 0)) { |
| sb.append(c); |
| expecting = (CHAR | STARTQUOTE | DELIMITER); |
| } else if ((expecting & CHAR) > 0) { |
| sb.append(c); |
| } else { |
| throw new IllegalArgumentException("Invalid delimited string: " + value); |
| } |
| } |
| |
| if (sb.length() > 0) { |
| list.add(sb.toString().trim()); |
| } |
| |
| if ( list.size() == 0 ) { |
| return null; |
| } |
| return list.toArray(new String[list.size()]); |
| } |
| |
| protected static final class PathImpl implements ManifestHeader.Entry { |
| |
| private final String value; |
| |
| private NameValuePair[] attributes; |
| private NameValuePair[] directives; |
| |
| public PathImpl(final String path) { |
| this.value = path; |
| } |
| |
| public void init(NameValuePair[] dirs, NameValuePair[] attrs) { |
| this.directives = dirs; |
| this.attributes = attrs; |
| } |
| |
| /** |
| * @see org.apache.sling.commons.osgi.ManifestHeader.Entry#getAttributes() |
| */ |
| public NameValuePair[] getAttributes() { |
| return this.attributes; |
| } |
| |
| /** |
| * @see org.apache.sling.commons.osgi.ManifestHeader.Entry#getDirectives() |
| */ |
| public NameValuePair[] getDirectives() { |
| return this.directives; |
| } |
| |
| /** |
| * @see org.apache.sling.commons.osgi.ManifestHeader.Entry#getValue() |
| */ |
| public String getValue() { |
| return this.value; |
| } |
| |
| public String getAttributeValue(String name) { |
| String v = null; |
| int index = 0; |
| while ( v == null && index < attributes.length ) { |
| if ( attributes[index].getName().equals(name) ) { |
| v = attributes[index].getValue(); |
| } |
| index++; |
| } |
| return v; |
| } |
| |
| public String getDirectiveValue(String name) { |
| String v = null; |
| int index = 0; |
| while ( v == null && index < directives.length ) { |
| if ( directives[index].getName().equals(name) ) { |
| v = directives[index].getValue(); |
| } |
| index++; |
| } |
| return v; |
| } |
| } |
| } |