001: /**********************************************************************
002: Copyright (c) 2006 Erik Bengtson and others. All rights reserved.
003: Licensed under the Apache License, Version 2.0 (the "License");
004: you may not use this file except in compliance with the License.
005: You may obtain a copy of the License at
006:
007: http://www.apache.org/licenses/LICENSE-2.0
008:
009: Unless required by applicable law or agreed to in writing, software
010: distributed under the License is distributed on an "AS IS" BASIS,
011: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: See the License for the specific language governing permissions and
013: limitations under the License.
014:
015:
016: Contributors:
017: ...
018: **********************************************************************/package org.jpox.plugin;
019:
020: import java.io.InputStreamReader;
021: import java.math.BigInteger;
022: import java.net.URL;
023: import java.text.CharacterIterator;
024: import java.text.StringCharacterIterator;
025: import java.util.ArrayList;
026: import java.util.Collections;
027: import java.util.HashMap;
028: import java.util.List;
029: import java.util.Map;
030: import java.util.StringTokenizer;
031: import java.util.jar.Manifest;
032:
033: import javax.xml.parsers.DocumentBuilder;
034: import javax.xml.parsers.DocumentBuilderFactory;
035: import javax.xml.parsers.ParserConfigurationException;
036:
037: import org.jpox.ClassLoaderResolver;
038: import org.jpox.exceptions.JPOXException;
039: import org.jpox.exceptions.JPOXUserException;
040: import org.jpox.util.JPOXLogger;
041: import org.jpox.util.Localiser;
042: import org.w3c.dom.Element;
043: import org.w3c.dom.NamedNodeMap;
044: import org.w3c.dom.Node;
045: import org.w3c.dom.NodeList;
046: import org.w3c.dom.Text;
047: import org.xml.sax.InputSource;
048:
049: /**
050: * Parser for manifest.mf and plugin.xml files
051: */
052: class PluginParser {
053: protected static final Localiser LOCALISER = Localiser
054: .getInstance("org.jpox.Localisation");
055:
056: public static Bundle parseManifest(Manifest mf, URL fileUrl) {
057: String symbolicName = getBundleSymbolicName(mf, null);
058: String bundleVersion = getBundleVersion(mf, null);
059: String bundleName = getBundleName(mf, null);
060: String bundleVendor = getBundleVendor(mf, null);
061: Bundle bundle = new Bundle(symbolicName, bundleName,
062: bundleVendor, bundleVersion, fileUrl);
063: bundle.setRequireBundle(getRequireBundle(mf));
064: return bundle;
065: }
066:
067: /**
068: * Accessor for the Bundle-Name from the manifest.mf file
069: * @param mf the manifest
070: * @return the Set with BundleDescription
071: */
072: private static List getRequireBundle(Manifest mf) {
073: String str = mf.getMainAttributes().getValue("Require-Bundle");
074: if (str == null || str.length() < 1) {
075: return Collections.EMPTY_LIST;
076: }
077: Parser p = new Parser(str);
078: List requiredBundle = new ArrayList();
079: String bundleSymbolicName = p.parseSymbolicName();
080: while (bundleSymbolicName != null) {
081: Bundle.BundleDescription bd = new Bundle.BundleDescription();
082: bd.setBundleSymbolicName(bundleSymbolicName);
083: bd.setParameters(p.parseParameters());
084: bundleSymbolicName = p.parseSymbolicName();
085: requiredBundle.add(bd);
086: }
087: return requiredBundle;
088: }
089:
090: /**
091: * Method to parse ExtensionPoints from plug-in file
092: * @param rootElement the root element of the plugin xml
093: * @param plugin the plugin bundle
094: * @param clr the ClassLoaderResolver
095: * @return a List of extensionPoints, if any
096: * @throws JPOXException if an error occurs during parsing
097: */
098: private static List parseExtensionPoints(Element rootElement,
099: Bundle plugin, ClassLoaderResolver clr) {
100: List extensionPoints = new ArrayList();
101: try {
102: NodeList elements = rootElement
103: .getElementsByTagName("extension-point");
104: for (int i = 0; i < elements.getLength(); i++) {
105: Element element = (Element) elements.item(i);
106: String id = element.getAttribute("id").trim();
107: String name = element.getAttribute("name");
108: String schema = element.getAttribute("schema");
109: extensionPoints.add(new ExtensionPoint(id, name, clr
110: .getResource(schema, null), plugin));
111: }
112: } catch (JPOXException ex) {
113: throw ex;
114: }
115: return extensionPoints;
116: }
117:
118: /**
119: * Method to parse Extensions from plug-in file
120: * @param rootElement the root element of the plugin xml
121: * @param plugin the plugin bundle
122: * @param clr the ClassLoaderResolver
123: * @return a List of extensions, if any
124: * @throws JPOXException if an error occurs during parsing
125: */
126: private static List parseExtensions(Element rootElement,
127: Bundle plugin, ClassLoaderResolver clr) {
128: List extensions = new ArrayList();
129: try {
130: NodeList elements = rootElement
131: .getElementsByTagName("extension");
132: for (int i = 0; i < elements.getLength(); i++) {
133: Element element = (Element) elements.item(i);
134: Extension ex = new Extension(element
135: .getAttribute("point"), plugin);
136: NodeList elms = element.getChildNodes();
137: extensions.add(ex);
138: for (int e = 0; e < elms.getLength(); e++) {
139: if (elms.item(e) instanceof Element) {
140: ex
141: .addConfigurationElement(parseConfigurationElement(
142: ex, (Element) elms.item(e),
143: null));
144: }
145: }
146: }
147: } catch (JPOXException ex) {
148: throw ex;
149: }
150: return extensions;
151: }
152:
153: /**
154: * Accessor for the Bundle-SymbolicName from the manifest.mf file
155: * @param mf the manifest
156: * @param defaultValue a default value, in case no symbolic name found in manifest
157: * @return the bundle symbolic name
158: */
159: private static String getBundleSymbolicName(Manifest mf,
160: String defaultValue) {
161: if (mf == null) {
162: return defaultValue;
163: }
164: String name = mf.getMainAttributes().getValue(
165: "Bundle-SymbolicName");
166: if (name == null) {
167: return defaultValue;
168: }
169: StringTokenizer token = new StringTokenizer(name, ";");
170: return token.nextToken().trim();
171: }
172:
173: /**
174: * Accessor for the Bundle-Name from the manifest.mf file
175: * @param mf the manifest
176: * @param defaultValue a default value, in case no name found in manifest
177: * @return the bundle name
178: */
179: private static String getBundleName(Manifest mf, String defaultValue) {
180: if (mf == null) {
181: return defaultValue;
182: }
183: String name = mf.getMainAttributes().getValue("Bundle-Name");
184: if (name == null) {
185: return defaultValue;
186: }
187: return name;
188: }
189:
190: /**
191: * Accessor for the Bundle-Vendor from the manifest.mf file
192: * @param mf the manifest
193: * @param defaultValue a default value, in case no vendor found in manifest
194: * @return the bundle vendor
195: */
196: private static String getBundleVendor(Manifest mf,
197: String defaultValue) {
198: if (mf == null) {
199: return defaultValue;
200: }
201: String vendor = mf.getMainAttributes()
202: .getValue("Bundle-Vendor");
203: if (vendor == null) {
204: return defaultValue;
205: }
206: return vendor;
207: }
208:
209: /**
210: * Accessor for the Bundle-Version from the manifest.mf file
211: * @param mf the manifest
212: * @param defaultValue a default value, in case no version found in manifest
213: * @return the bundle version
214: */
215: private static String getBundleVersion(Manifest mf,
216: String defaultValue) {
217: if (mf == null) {
218: return defaultValue;
219: }
220: String version = mf.getMainAttributes().getValue(
221: "Bundle-Version");
222: if (version == null) {
223: return defaultValue;
224: }
225: return version;
226: }
227:
228: /**
229: * Method to parse Extensions in plug-in file
230: * @param mgr the PluginManager
231: * @param fileUrl URL of the plugin.xml file
232: * @param clr the ClassLoaderResolver
233: * @return array of 2 elements. first element is a List of extensionPoints, and 2nd element is a List of Extension
234: * @throws JPOXException if an error occurs during parsing
235: */
236: public static List[] parsePluginElements(PluginRegistry mgr,
237: URL fileUrl, Bundle plugin, ClassLoaderResolver clr) {
238: List extensionPoints = Collections.EMPTY_LIST;
239: List extensions = Collections.EMPTY_LIST;
240: try {
241: final DocumentBuilderFactory factory = DocumentBuilderFactory
242: .newInstance();
243: final DocumentBuilder db = factory.newDocumentBuilder();
244: try {
245: Element rootElement = db.parse(
246: new InputSource(new InputStreamReader(fileUrl
247: .openStream()))).getDocumentElement();
248:
249: if (JPOXLogger.PLUGIN.isDebugEnabled()) {
250: JPOXLogger.PLUGIN.debug(LOCALISER.msg("024003",
251: fileUrl.toString()));
252: }
253: extensionPoints = parseExtensionPoints(rootElement,
254: plugin, clr);
255:
256: if (JPOXLogger.PLUGIN.isDebugEnabled()) {
257: JPOXLogger.PLUGIN.debug(LOCALISER.msg("024004",
258: fileUrl.toString()));
259: }
260: extensions = parseExtensions(rootElement, plugin, clr);
261: } catch (JPOXException ex) {
262: throw ex;
263: } catch (Exception e) {
264: JPOXLogger.PLUGIN.error(LOCALISER.msg("024000", fileUrl
265: .getFile()));
266: }
267: } catch (ParserConfigurationException e1) {
268: JPOXLogger.PLUGIN.error(LOCALISER.msg("024001", fileUrl
269: .getFile(), e1.getMessage()));
270: }
271: return new List[] { extensionPoints, extensions };
272: }
273:
274: /**
275: * Parses the current element and children, creating a ConfigurationElement object
276: * @param ex the {@link Extension}
277: * @param element the current element
278: * @param parent the parent. null if the parent is Extension
279: * @return the ConfigurationElement for the element
280: */
281: private static ConfigurationElement parseConfigurationElement(
282: Extension ex, Element element, ConfigurationElement parent) {
283: ConfigurationElement confElm = new ConfigurationElement(ex,
284: element.getNodeName(), parent);
285: NamedNodeMap attributes = element.getAttributes();
286: for (int i = 0; i < attributes.getLength(); i++) {
287: Node attribute = attributes.item(i);
288: confElm.putAttribute(attribute.getNodeName(), attribute
289: .getNodeValue());
290: }
291: NodeList elements = element.getChildNodes();
292: for (int i = 0; i < elements.getLength(); i++) {
293: if (elements.item(i) instanceof Element) {
294: Element elm = (Element) elements.item(i);
295: ConfigurationElement child = parseConfigurationElement(
296: ex, elm, confElm);
297: confElm.addConfigurationElement(child);
298: } else if (elements.item(i) instanceof Text) {
299: confElm.setText(elements.item(i).getNodeValue());
300: }
301: }
302: return confElm;
303: }
304:
305: /**
306: * Parse a Version Range as per OSGi spec 3.0 $3.2.5
307: * @param interval the interval string
308: * @return
309: */
310: public static Bundle.BundleVersionRange parseVersionRange(
311: String interval) {
312: Parser p = new Parser(interval);
313: Bundle.BundleVersionRange versionRange = new Bundle.BundleVersionRange();
314:
315: if (p.parseChar('[')) {
316: //inclusive
317: versionRange.floor_inclusive = true;
318: } else if (p.parseChar('(')) {
319: //exclusive
320: versionRange.floor_inclusive = false;
321: }
322: versionRange.floor = new Bundle.BundleVersion();
323: versionRange.floor.major = p.parseIntegerLiteral().intValue();
324: if (p.parseChar('.')) {
325: versionRange.floor.minor = p.parseIntegerLiteral()
326: .intValue();
327: }
328: if (p.parseChar('.')) {
329: versionRange.floor.micro = p.parseIntegerLiteral()
330: .intValue();
331: }
332: if (p.parseChar('.')) {
333: versionRange.floor.qualifier = p.parseIdentifier();
334: }
335: if (p.parseChar(',')) {
336: versionRange.ceiling = new Bundle.BundleVersion();
337: versionRange.ceiling.major = p.parseIntegerLiteral()
338: .intValue();
339: if (p.parseChar('.')) {
340: versionRange.ceiling.minor = p.parseIntegerLiteral()
341: .intValue();
342: }
343: if (p.parseChar('.')) {
344: versionRange.ceiling.micro = p.parseIntegerLiteral()
345: .intValue();
346: }
347: if (p.parseChar('.')) {
348: versionRange.ceiling.qualifier = p.parseIdentifier();
349: }
350: if (p.parseChar(']')) {
351: //inclusive
352: versionRange.ceiling_inclusive = true;
353: } else if (p.parseChar(')')) {
354: //exclusive
355: versionRange.ceiling_inclusive = false;
356: }
357: }
358: return versionRange;
359: }
360:
361: /**
362: * Parser for a list of Bundle-Description
363: *
364: * @version $Revision: 1.15 $
365: **/
366: public static class Parser {
367: private final String input;
368: protected final CharacterIterator ci;
369:
370: /**
371: * Constructor
372: * @param input The input string
373: **/
374: public Parser(String input) {
375: this .input = input;
376:
377: ci = new StringCharacterIterator(input);
378: }
379:
380: /**
381: * Accessor for the input string.
382: * @return The input string.
383: */
384: public String getInput() {
385: return input;
386: }
387:
388: /**
389: * Accessor for the current index in the input string.
390: * @return The current index.
391: */
392: public int getIndex() {
393: return ci.getIndex();
394: }
395:
396: /**
397: * Skip over any whitespace from the current position.
398: * @return The new position
399: */
400: public int skipWS() {
401: int startIdx = ci.getIndex();
402: char c = ci.current();
403:
404: while (Character.isWhitespace(c) || c == '\t' || c == '\f'
405: || c == '\n' || c == '\r' || c == '\u0009'
406: || c == '\u000c' || c == '\u0020' || c == '\11'
407: || c == '\12' || c == '\14' || c == '\15'
408: || c == '\40') {
409: c = ci.next();
410: }
411:
412: return startIdx;
413: }
414:
415: /**
416: * Check if END OF TEXT is reach
417: * @return true if END OF TEXT is reach
418: */
419: public boolean parseEOS() {
420: skipWS();
421:
422: return ci.current() == CharacterIterator.DONE;
423: }
424:
425: /**
426: * Check if char <code>c</code> is found
427: * @param c the Character to find
428: * @return true if <code>c</code> is found
429: */
430: public boolean parseChar(char c) {
431: skipWS();
432:
433: if (ci.current() == c) {
434: ci.next();
435: return true;
436: } else {
437: return false;
438: }
439: }
440:
441: /**
442: * Check if char <code>c</code> is found
443: * @param c the Character to find
444: * @param unlessFollowedBy the character to validate it does not follow <code>c</code>
445: * @return true if <code>c</code> is found and not followed by <code>unlessFollowedBy</code>
446: */
447: public boolean parseChar(char c, char unlessFollowedBy) {
448: int savedIdx = skipWS();
449:
450: if (ci.current() == c && ci.next() != unlessFollowedBy) {
451: return true;
452: } else {
453: ci.setIndex(savedIdx);
454: return false;
455: }
456: }
457:
458: /**
459: * Parse an integer number from the current position.
460: * @return The integer number parsed (null if not valid).
461: */
462: public BigInteger parseIntegerLiteral() {
463: int savedIdx = skipWS();
464:
465: StringBuffer digits = new StringBuffer();
466: int radix;
467: char c = ci.current();
468:
469: if (c == '0') {
470: c = ci.next();
471:
472: if (c == 'x' || c == 'X') {
473: radix = 16;
474: c = ci.next();
475:
476: while (isHexDigit(c)) {
477: digits.append(c);
478: c = ci.next();
479: }
480: } else if (isOctDigit(c)) {
481: radix = 8;
482:
483: do {
484: digits.append(c);
485: c = ci.next();
486: } while (isOctDigit(c));
487: } else {
488: radix = 10;
489: digits.append('0');
490: }
491: } else {
492: radix = 10;
493:
494: while (isDecDigit(c)) {
495: digits.append(c);
496: c = ci.next();
497: }
498: }
499:
500: if (digits.length() == 0) {
501: ci.setIndex(savedIdx);
502: return null;
503: }
504:
505: if (c == 'l' || c == 'L') {
506: ci.next();
507: }
508:
509: return new BigInteger(digits.toString(), radix);
510: }
511:
512: /**
513: * Check if String <code>s</code> is found
514: * @param s the String to find
515: * @return true if <code>s</code> is found
516: */
517: public boolean parseString(String s) {
518: int savedIdx = skipWS();
519:
520: int len = s.length();
521: char c = ci.current();
522:
523: for (int i = 0; i < len; ++i) {
524: if (c != s.charAt(i)) {
525: ci.setIndex(savedIdx);
526: return false;
527: }
528:
529: c = ci.next();
530: }
531:
532: return true;
533: }
534:
535: /**
536: * Check if String <code>s</code> is found ignoring the case
537: * @param s the String to find
538: * @return true if <code>s</code> is found
539: */
540: public boolean parseStringIgnoreCase(String s) {
541: String lowerCasedString = s.toLowerCase();
542:
543: int savedIdx = skipWS();
544:
545: int len = lowerCasedString.length();
546: char c = ci.current();
547:
548: for (int i = 0; i < len; ++i) {
549: if (Character.toLowerCase(c) != lowerCasedString
550: .charAt(i)) {
551: ci.setIndex(savedIdx);
552: return false;
553: }
554:
555: c = ci.next();
556: }
557:
558: return true;
559: }
560:
561: /**
562: * Parse a java identifier from the current position.
563: * @return The identifier
564: */
565: public String parseIdentifier() {
566: skipWS();
567: char c = ci.current();
568:
569: if (!Character.isJavaIdentifierStart(c)) {
570: return null;
571: }
572:
573: StringBuffer id = new StringBuffer();
574: id.append(c);
575: //- hifen symbol is valid according OSGi specification
576: while (Character.isJavaIdentifierPart(c = ci.next())
577: || c == '-') {
578: id.append(c);
579: }
580:
581: return id.toString();
582: }
583:
584: /**
585: * Parse an OSGi interval from the current position.
586: * @return The interval
587: */
588: public String parseInterval() {
589: skipWS();
590: char c = ci.current();
591:
592: StringBuffer id = new StringBuffer();
593:
594: while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
595: || (c >= '0' && c <= '9') || c == '.' || c == '_'
596: || c == '-' || c == '[' || c == ']' || c == '('
597: || c == ')') {
598: id.append(c);
599: c = ci.next();
600: }
601:
602: return id.toString();
603: }
604:
605: /**
606: * Parses the text string (up to the next space) and
607: * returns it. The name includes '.' characters.
608: * This can be used, for example, when parsing a class name wanting to
609: * read in the full name (including package) so that it can then be
610: * checked for existence in the CLASSPATH.
611: * @return The name
612: */
613: public String parseName() {
614: int savedIdx = skipWS();
615: String id;
616:
617: if ((id = parseIdentifier()) == null) {
618: return null;
619: }
620:
621: StringBuffer qn = new StringBuffer(id);
622:
623: while (parseChar('.')) {
624: if ((id = parseIdentifier()) == null) {
625: ci.setIndex(savedIdx);
626: return null;
627: }
628:
629: qn.append('.').append(id);
630: }
631:
632: return qn.toString();
633: }
634:
635: /**
636: * Utility to return if a character is a decimal digit.
637: * @param c The character
638: * @return Whether it is a decimal digit
639: */
640: private final boolean isDecDigit(char c) {
641: return c >= '0' && c <= '9';
642: }
643:
644: /**
645: * Utility to return if a character is a octal digit.
646: * @param c The character
647: * @return Whether it is a octal digit
648: */
649: private final boolean isOctDigit(char c) {
650: return c >= '0' && c <= '7';
651: }
652:
653: /**
654: * Utility to return if a character is a hexadecimal digit.
655: * @param c The character
656: * @return Whether it is a hexadecimal digit
657: */
658: private final boolean isHexDigit(char c) {
659: return c >= '0' && c <= '9' || c >= 'a' && c <= 'f'
660: || c >= 'A' && c <= 'F';
661: }
662:
663: /**
664: * Utility to return if the next non-whitespace character is a single quote.
665: * @return Whether it is a single quote at the current point (ignoring whitespace)
666: */
667: public boolean nextIsSingleQuote() {
668: skipWS();
669: return (ci.current() == '\'');
670: }
671:
672: /**
673: * Utility to return if the next character is a dot.
674: * @return Whether it is a dot at the current point
675: */
676: public boolean nextIsDot() {
677: return (ci.current() == '.');
678: }
679:
680: /**
681: * Utility to return if the next character is a comma.
682: * @return Whether it is a dot at the current point
683: */
684: public boolean nextIsComma() {
685: return (ci.current() == ',');
686: }
687:
688: /**
689: * Utility to return if the next character is a semi-colon.
690: * @return Whether it is a semi-colon at the current point
691: */
692: public boolean nextIsSemiColon() {
693: return (ci.current() == ';');
694: }
695:
696: /**
697: * Parse a String literal
698: * @return the String parsed. null if single quotes or double quotes is found
699: * @throws JPOXUserException if an invalid character is found or the CharacterIterator is finished
700: */
701: public String parseStringLiteral() {
702: skipWS();
703:
704: // Strings can be surrounded by single or double quotes
705: char quote = ci.current();
706: if (quote != '"' && quote != '\'') {
707: return null;
708: }
709:
710: StringBuffer lit = new StringBuffer();
711: char c;
712:
713: while ((c = ci.next()) != quote) {
714: if (c == CharacterIterator.DONE) {
715: throw new JPOXUserException(
716: "Invalid string literal: " + input);
717: }
718:
719: if (c == '\\') {
720: c = parseEscapedCharacter();
721: }
722:
723: lit.append(c);
724: }
725:
726: ci.next();
727:
728: return lit.toString();
729: }
730:
731: /**
732: * Parse a escaped character
733: * @return the escaped char
734: * @throws JPOXUserException if a escaped character is not valid
735: */
736: private char parseEscapedCharacter() {
737: char c;
738:
739: if (isOctDigit(c = ci.next())) {
740: int i = (c - '0');
741:
742: if (isOctDigit(c = ci.next())) {
743: i = i * 8 + (c - '0');
744:
745: if (isOctDigit(c = ci.next())) {
746: i = i * 8 + (c - '0');
747: } else {
748: ci.previous();
749: }
750: } else {
751: ci.previous();
752: }
753:
754: if (i > 0xff) {
755: throw new JPOXUserException(
756: "Invalid character escape: '\\"
757: + Integer.toOctalString(i) + "'");
758: }
759:
760: return (char) i;
761: } else {
762: switch (c) {
763: case 'b':
764: return '\b';
765: case 't':
766: return '\t';
767: case 'n':
768: return '\n';
769: case 'f':
770: return '\f';
771: case 'r':
772: return '\r';
773: case '"':
774: return '"';
775: case '\'':
776: return '\'';
777: case '\\':
778: return '\\';
779: default:
780: throw new JPOXUserException(
781: "Invalid character escape: '\\" + c + "'");
782: }
783: }
784: }
785:
786: public String remaining() {
787: StringBuffer sb = new StringBuffer();
788: char c = ci.current();
789: while (c != CharacterIterator.DONE) {
790: sb.append(c);
791: c = ci.next();
792: }
793: return sb.toString();
794: }
795:
796: public String toString() {
797: return input;
798: }
799:
800: public Map parseParameters() {
801: skipWS();
802: Map paramaters = new HashMap();
803: while (nextIsSemiColon()) {
804: parseChar(';');
805: skipWS();
806: String name = parseName();
807: skipWS();
808: if (!parseString(":=") && !parseString("=")) {
809: throw new JPOXUserException(
810: "Expected := or = symbols but found \""
811: + remaining() + "\" at position "
812: + this .getIndex() + " of text \""
813: + input + "\"");
814: }
815: String argument = parseStringLiteral();
816: if (argument == null) {
817: argument = parseIdentifier();
818: }
819: if (argument == null) {
820: argument = parseInterval();
821: }
822: paramaters.put(name, argument);
823: skipWS();
824: }
825: return paramaters;
826: }
827:
828: public String parseSymbolicName() {
829: if (nextIsComma()) {
830: parseChar(',');
831: }
832: String name = parseName();
833: if (name == null && !parseEOS()) {
834: throw new JPOXUserException(
835: "Invalid characters found \"" + remaining()
836: + "\" at position " + this .getIndex()
837: + " of text \"" + input + "\"");
838: }
839: return name;
840: }
841: }
842: }
|