001: /*
002: * $Id: XMLEntityStorage.java,v 1.4 2006/11/29 22:01:32 spericas Exp $
003: */
004:
005: /*
006: * The contents of this file are subject to the terms
007: * of the Common Development and Distribution License
008: * (the License). You may not use this file except in
009: * compliance with the License.
010: *
011: * You can obtain a copy of the license at
012: * https://glassfish.dev.java.net/public/CDDLv1.0.html.
013: * See the License for the specific language governing
014: * permissions and limitations under the License.
015: *
016: * When distributing Covered Code, include this CDDL
017: * Header Notice in each file and include the License file
018: * at https://glassfish.dev.java.net/public/CDDLv1.0.html.
019: * If applicable, add the following below the CDDL Header,
020: * with the fields enclosed by brackets [] replaced by
021: * you own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * [Name of File] [ver.__] [Date]
025: *
026: * Copyright 2006 Sun Microsystems Inc. All Rights Reserved
027: */
028:
029: package com.sun.xml.stream;
030:
031: import java.util.Hashtable;
032:
033: import com.sun.xml.stream.xerces.impl.msg.XMLMessageFormatter;
034: import com.sun.xml.stream.xerces.util.URI;
035: import com.sun.xml.stream.xerces.util.XMLResourceIdentifierImpl;
036: import com.sun.xml.stream.xerces.xni.parser.XMLComponentManager;
037: import com.sun.xml.stream.xerces.xni.parser.XMLConfigurationException;
038:
039: /**
040: *
041: * @author K.Venugopal SUN Microsystems
042: * @author Neeraj Bajaj SUN Microsystems
043: * @author Andy Clark, IBM
044: *
045: */
046: public class XMLEntityStorage {
047:
048: /** Property identifier: error reporter. */
049: protected static final String ERROR_REPORTER = Constants.XERCES_PROPERTY_PREFIX
050: + Constants.ERROR_REPORTER_PROPERTY;
051:
052: /** Feature identifier: warn on duplicate EntityDef */
053: protected static final String WARN_ON_DUPLICATE_ENTITYDEF = Constants.XERCES_FEATURE_PREFIX
054: + Constants.WARN_ON_DUPLICATE_ENTITYDEF_FEATURE;
055:
056: /** warn on duplicate Entity declaration.
057: * http://apache.org/xml/features/warn-on-duplicate-entitydef
058: */
059: protected boolean fWarnDuplicateEntityDef;
060:
061: /** Entities. */
062: protected Hashtable fEntities = new Hashtable();
063:
064: protected Entity.ScannedEntity fCurrentEntity;
065:
066: private XMLEntityManager fEntityManager;
067: /**
068: * Error reporter. This property identifier is:
069: * http://apache.org/xml/properties/internal/error-reporter
070: */
071: protected XMLErrorReporter fErrorReporter;
072: protected PropertyManager fPropertyManager;
073:
074: /** Creates a new instance of XMLEntityStorage */
075: public XMLEntityStorage(PropertyManager propertyManager) {
076: fPropertyManager = propertyManager;
077: }
078:
079: /** Creates a new instance of XMLEntityStorage */
080: /*public XMLEntityStorage(Entity.ScannedEntity currentEntity) {
081: fCurrentEntity = currentEntity ;*/
082: public XMLEntityStorage(XMLEntityManager entityManager) {
083: fEntityManager = entityManager;
084: }
085:
086: public void reset(PropertyManager propertyManager) {
087:
088: fErrorReporter = (XMLErrorReporter) propertyManager
089: .getProperty(Constants.XERCES_PROPERTY_PREFIX
090: + Constants.ERROR_REPORTER_PROPERTY);
091: fEntities.clear();
092: fCurrentEntity = null;
093:
094: }
095:
096: public void reset() {
097: fEntities.clear();
098: fCurrentEntity = null;
099: }
100:
101: /**
102: * Resets the component. The component can query the component manager
103: * about any features and properties that affect the operation of the
104: * component.
105: *
106: * @param componentManager The component manager.
107: *
108: * @throws SAXException Thrown by component on initialization error.
109: * For example, if a feature or property is
110: * required for the operation of the component, the
111: * component manager may throw a
112: * SAXNotRecognizedException or a
113: * SAXNotSupportedException.
114: */
115: public void reset(XMLComponentManager componentManager)
116: throws XMLConfigurationException {
117:
118: // xerces features
119:
120: try {
121: fWarnDuplicateEntityDef = componentManager
122: .getFeature(WARN_ON_DUPLICATE_ENTITYDEF);
123: } catch (XMLConfigurationException e) {
124: fWarnDuplicateEntityDef = false;
125: }
126:
127: fErrorReporter = (XMLErrorReporter) componentManager
128: .getProperty(ERROR_REPORTER);
129:
130: fEntities.clear();
131: fCurrentEntity = null;
132:
133: } // reset(XMLComponentManager)
134:
135: /**
136: * Returns the hashtable of declared entities.
137: * <p>
138: * <strong>REVISIT:</strong>
139: * This should be done the "right" way by designing a better way to
140: * enumerate the declared entities. For now, this method is needed
141: * by the constructor that takes an XMLEntityManager parameter.
142: * XXX Making this method public, return all the declared entities.
143: * @return Hashtable hastable containing all the declared entities.
144: */
145: public Hashtable getDeclaredEntities() {
146: return fEntities;
147: } // getDeclaredEntities():Hashtable
148:
149: /**
150: * Adds an internal entity declaration.
151: * <p>
152: * <strong>Note:</strong> This method ignores subsequent entity
153: * declarations.
154: * <p>
155: * <strong>Note:</strong> The name should be a unique symbol. The
156: * SymbolTable can be used for this purpose.
157: *
158: * @param name The name of the entity.
159: * @param text The text of the entity.
160: *
161: * @see SymbolTable
162: */
163: public void addInternalEntity(String name, String text) {
164: if (!fEntities.containsKey(name)) {
165: //some times we need information, if the current entity is part of external subset..
166: fCurrentEntity = fEntityManager.getCurrentEntity();
167: Entity entity = new Entity.InternalEntity(name, text, false);
168: //(fCurrentEntity == null) ? fasle : fCurrentEntity.isEntityDeclInExternalSubset());
169: fEntities.put(name, entity);
170: } else {
171: if (fWarnDuplicateEntityDef) {
172: fErrorReporter.reportError(
173: XMLMessageFormatter.XML_DOMAIN,
174: "MSG_DUPLICATE_ENTITY_DEFINITION",
175: new Object[] { name },
176: XMLErrorReporter.SEVERITY_WARNING);
177: }
178: }
179:
180: } // addInternalEntity(String,String)
181:
182: /**
183: * Adds an external entity declaration.
184: * <p>
185: * <strong>Note:</strong> This method ignores subsequent entity
186: * declarations.
187: * <p>
188: * <strong>Note:</strong> The name should be a unique symbol. The
189: * SymbolTable can be used for this purpose.
190: *
191: * @param name The name of the entity.
192: * @param publicId The public identifier of the entity.
193: * @param literalSystemId The system identifier of the entity.
194: * @param baseSystemId The base system identifier of the entity.
195: * This is the system identifier of the entity
196: * where <em>the entity being added</em> and
197: * is used to expand the system identifier when
198: * the system identifier is a relative URI.
199: * When null the system identifier of the first
200: * external entity on the stack is used instead.
201: *
202: * @see SymbolTable
203: */
204: public void addExternalEntity(String name, String publicId,
205: String literalSystemId, String baseSystemId) {
206: if (!fEntities.containsKey(name)) {
207: if (baseSystemId == null) {
208: // search for the first external entity on the stack
209: //xxx commenting the 'size' variable..
210: /**
211: * int size = fEntityStack.size();
212: * if (size == 0 && fCurrentEntity != null && fCurrentEntity.entityLocation != null) {
213: * baseSystemId = fCurrentEntity.entityLocation.getExpandedSystemId();
214: * }
215: */
216:
217: //xxx we need to have information about the current entity.
218: if (fCurrentEntity != null
219: && fCurrentEntity.entityLocation != null) {
220: baseSystemId = fCurrentEntity.entityLocation
221: .getExpandedSystemId();
222: }
223: /**
224: * for (int i = size - 1; i >= 0 ; i--) {
225: * ScannedEntity externalEntity =
226: * (ScannedEntity)fEntityStack.elementAt(i);
227: * if (externalEntity.entityLocation != null && externalEntity.entityLocation.getExpandedSystemId() != null) {
228: * baseSystemId = externalEntity.entityLocation.getExpandedSystemId();
229: * break;
230: * }
231: * }
232: */
233: }
234:
235: fCurrentEntity = fEntityManager.getCurrentEntity();
236: Entity entity = new Entity.ExternalEntity(name,
237: new XMLResourceIdentifierImpl(publicId,
238: literalSystemId, baseSystemId,
239: expandSystemId(literalSystemId,
240: baseSystemId)), null, true);
241: //TODO :: Forced to pass true above remove it.
242: //(fCurrentEntity == null) ? fasle : fCurrentEntity.isEntityDeclInExternalSubset());
243: // null, fCurrentEntity.isEntityDeclInExternalSubset());
244: fEntities.put(name, entity);
245: } else {
246: if (fWarnDuplicateEntityDef) {
247: fErrorReporter.reportError(
248: XMLMessageFormatter.XML_DOMAIN,
249: "MSG_DUPLICATE_ENTITY_DEFINITION",
250: new Object[] { name },
251: XMLErrorReporter.SEVERITY_WARNING);
252: }
253: }
254:
255: } // addExternalEntity(String,String,String,String)
256:
257: /**
258: * Checks whether an entity given by name is external.
259: *
260: * @param entityName The name of the entity to check.
261: * @returns True if the entity is external, false otherwise
262: * (including when the entity is not declared).
263: */
264: public boolean isExternalEntity(String entityName) {
265:
266: Entity entity = (Entity) fEntities.get(entityName);
267: if (entity == null) {
268: return false;
269: }
270: return entity.isExternal();
271: }
272:
273: /**
274: * Checks whether the declaration of an entity given by name is
275: * // in the external subset.
276: *
277: * @param entityName The name of the entity to check.
278: * @returns True if the entity was declared in the external subset, false otherwise
279: * (including when the entity is not declared).
280: */
281: public boolean isEntityDeclInExternalSubset(String entityName) {
282:
283: Entity entity = (Entity) fEntities.get(entityName);
284: if (entity == null) {
285: return false;
286: }
287: return entity.isEntityDeclInExternalSubset();
288: }
289:
290: /**
291: * Adds an unparsed entity declaration.
292: * <p>
293: * <strong>Note:</strong> This method ignores subsequent entity
294: * declarations.
295: * <p>
296: * <strong>Note:</strong> The name should be a unique symbol. The
297: * SymbolTable can be used for this purpose.
298: *
299: * @param name The name of the entity.
300: * @param publicId The public identifier of the entity.
301: * @param systemId The system identifier of the entity.
302: * @param notation The name of the notation.
303: *
304: * @see SymbolTable
305: */
306: public void addUnparsedEntity(String name, String publicId,
307: String systemId, String baseSystemId, String notation) {
308:
309: fCurrentEntity = fEntityManager.getCurrentEntity();
310: if (!fEntities.containsKey(name)) {
311: Entity entity = new Entity.ExternalEntity(name,
312: new XMLResourceIdentifierImpl(publicId, systemId,
313: baseSystemId, null), notation, false);
314: // (fCurrentEntity == null) ? fasle : fCurrentEntity.isEntityDeclInExternalSubset());
315: // fCurrentEntity.isEntityDeclInExternalSubset());
316: fEntities.put(name, entity);
317: } else {
318: if (fWarnDuplicateEntityDef) {
319: fErrorReporter.reportError(
320: XMLMessageFormatter.XML_DOMAIN,
321: "MSG_DUPLICATE_ENTITY_DEFINITION",
322: new Object[] { name },
323: XMLErrorReporter.SEVERITY_WARNING);
324: }
325: }
326: } // addUnparsedEntity(String,String,String,String)
327:
328: /**
329: * Checks whether an entity given by name is unparsed.
330: *
331: * @param entityName The name of the entity to check.
332: * @returns True if the entity is unparsed, false otherwise
333: * (including when the entity is not declared).
334: */
335: public boolean isUnparsedEntity(String entityName) {
336:
337: Entity entity = (Entity) fEntities.get(entityName);
338: if (entity == null) {
339: return false;
340: }
341: return entity.isUnparsed();
342: }
343:
344: /**
345: * Checks whether an entity given by name is declared.
346: *
347: * @param entityName The name of the entity to check.
348: * @returns True if the entity is declared, false otherwise.
349: */
350: public boolean isDeclaredEntity(String entityName) {
351:
352: Entity entity = (Entity) fEntities.get(entityName);
353: return entity != null;
354: }
355:
356: /**
357: * Expands a system id and returns the system id as a URI, if
358: * it can be expanded. A return value of null means that the
359: * identifier is already expanded. An exception thrown
360: * indicates a failure to expand the id.
361: *
362: * @param systemId The systemId to be expanded.
363: *
364: * @return Returns the URI string representing the expanded system
365: * identifier. A null value indicates that the given
366: * system identifier is already expanded.
367: *
368: */
369: public static String expandSystemId(String systemId) {
370: return expandSystemId(systemId, null);
371: } // expandSystemId(String):String
372:
373: // current value of the "user.dir" property
374: private static String gUserDir;
375: // escaped value of the current "user.dir" property
376: private static String gEscapedUserDir;
377: // which ASCII characters need to be escaped
378: private static boolean gNeedEscaping[] = new boolean[128];
379: // the first hex character if a character needs to be escaped
380: private static char gAfterEscaping1[] = new char[128];
381: // the second hex character if a character needs to be escaped
382: private static char gAfterEscaping2[] = new char[128];
383: private static char[] gHexChs = { '0', '1', '2', '3', '4', '5',
384: '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
385: // initialize the above 3 arrays
386: static {
387: for (int i = 0; i <= 0x1f; i++) {
388: gNeedEscaping[i] = true;
389: gAfterEscaping1[i] = gHexChs[i >> 4];
390: gAfterEscaping2[i] = gHexChs[i & 0xf];
391: }
392: gNeedEscaping[0x7f] = true;
393: gAfterEscaping1[0x7f] = '7';
394: gAfterEscaping2[0x7f] = 'F';
395: char[] escChs = { ' ', '<', '>', '#', '%', '"', '{', '}', '|',
396: '\\', '^', '~', '[', ']', '`' };
397: int len = escChs.length;
398: char ch;
399: for (int i = 0; i < len; i++) {
400: ch = escChs[i];
401: gNeedEscaping[ch] = true;
402: gAfterEscaping1[ch] = gHexChs[ch >> 4];
403: gAfterEscaping2[ch] = gHexChs[ch & 0xf];
404: }
405: }
406:
407: // To escape the "user.dir" system property, by using %HH to represent
408: // special ASCII characters: 0x00~0x1F, 0x7F, ' ', '<', '>', '#', '%'
409: // and '"'. It's a static method, so needs to be synchronized.
410: // this method looks heavy, but since the system property isn't expected
411: // to change often, so in most cases, we only need to return the string
412: // that was escaped before.
413: // According to the URI spec, non-ASCII characters (whose value >= 128)
414: // need to be escaped too.
415: // REVISIT: don't know how to escape non-ASCII characters, especially
416: // which encoding to use. Leave them for now.
417: private static synchronized String getUserDir() {
418: // get the user.dir property
419: String userDir = "";
420: try {
421: userDir = System.getProperty("user.dir");
422: } catch (SecurityException se) {
423: }
424:
425: // return empty string if property value is empty string.
426: if (userDir.length() == 0)
427: return "";
428:
429: // compute the new escaped value if the new property value doesn't
430: // match the previous one
431: if (userDir.equals(gUserDir)) {
432: return gEscapedUserDir;
433: }
434:
435: // record the new value as the global property value
436: gUserDir = userDir;
437:
438: char separator = java.io.File.separatorChar;
439: userDir = userDir.replace(separator, '/');
440:
441: int len = userDir.length(), ch;
442: StringBuffer buffer = new StringBuffer(len * 3);
443: // change C:/blah to /C:/blah
444: if (len >= 2 && userDir.charAt(1) == ':') {
445: ch = Character.toUpperCase(userDir.charAt(0));
446: if (ch >= 'A' && ch <= 'Z') {
447: buffer.append('/');
448: }
449: }
450:
451: // for each character in the path
452: int i = 0;
453: for (; i < len; i++) {
454: ch = userDir.charAt(i);
455: // if it's not an ASCII character, break here, and use UTF-8 encoding
456: if (ch >= 128)
457: break;
458: if (gNeedEscaping[ch]) {
459: buffer.append('%');
460: buffer.append(gAfterEscaping1[ch]);
461: buffer.append(gAfterEscaping2[ch]);
462: // record the fact that it's escaped
463: } else {
464: buffer.append((char) ch);
465: }
466: }
467:
468: // we saw some non-ascii character
469: if (i < len) {
470: // get UTF-8 bytes for the remaining sub-string
471: byte[] bytes = null;
472: byte b;
473: try {
474: bytes = userDir.substring(i).getBytes("UTF-8");
475: } catch (java.io.UnsupportedEncodingException e) {
476: // should never happen
477: return userDir;
478: }
479: len = bytes.length;
480:
481: // for each byte
482: for (i = 0; i < len; i++) {
483: b = bytes[i];
484: // for non-ascii character: make it positive, then escape
485: if (b < 0) {
486: ch = b + 256;
487: buffer.append('%');
488: buffer.append(gHexChs[ch >> 4]);
489: buffer.append(gHexChs[ch & 0xf]);
490: } else if (gNeedEscaping[b]) {
491: buffer.append('%');
492: buffer.append(gAfterEscaping1[b]);
493: buffer.append(gAfterEscaping2[b]);
494: } else {
495: buffer.append((char) b);
496: }
497: }
498: }
499:
500: // change blah/blah to blah/blah/
501: if (!userDir.endsWith("/"))
502: buffer.append('/');
503:
504: gEscapedUserDir = buffer.toString();
505:
506: return gEscapedUserDir;
507: }
508:
509: /**
510: * Expands a system id and returns the system id as a URI, if
511: * it can be expanded. A return value of null means that the
512: * identifier is already expanded. An exception thrown
513: * indicates a failure to expand the id.
514: *
515: * @param systemId The systemId to be expanded.
516: *
517: * @return Returns the URI string representing the expanded system
518: * identifier. A null value indicates that the given
519: * system identifier is already expanded.
520: *
521: */
522: public static String expandSystemId(String systemId,
523: String baseSystemId) {
524:
525: // check for bad parameters id
526: if (systemId == null || systemId.length() == 0) {
527: return systemId;
528: }
529: // if id already expanded, return
530: try {
531: URI uri = new URI(systemId);
532: if (uri != null) {
533: return systemId;
534: }
535: } catch (URI.MalformedURIException e) {
536: // continue on...
537: }
538: // normalize id
539: String id = fixURI(systemId);
540:
541: // normalize base
542: URI base = null;
543: URI uri = null;
544: try {
545: if (baseSystemId == null || baseSystemId.length() == 0
546: || baseSystemId.equals(systemId)) {
547: String dir = getUserDir();
548: base = new URI("file", "", dir, null, null);
549: } else {
550: try {
551: base = new URI(fixURI(baseSystemId));
552: } catch (URI.MalformedURIException e) {
553: if (baseSystemId.indexOf(':') != -1) {
554: // for xml schemas we might have baseURI with
555: // a specified drive
556: base = new URI("file", "",
557: fixURI(baseSystemId), null, null);
558: } else {
559: String dir = getUserDir();
560: dir = dir + fixURI(baseSystemId);
561: base = new URI("file", "", dir, null, null);
562: }
563: }
564: }
565: // expand id
566: uri = new URI(base, id);
567: } catch (Exception e) {
568: // let it go through
569:
570: }
571:
572: if (uri == null) {
573: return systemId;
574: }
575: return uri.toString();
576:
577: } // expandSystemId(String,String):String
578:
579: //
580: // Protected static methods
581: //
582:
583: /**
584: * Fixes a platform dependent filename to standard URI form.
585: *
586: * @param str The string to fix.
587: *
588: * @return Returns the fixed URI string.
589: */
590: protected static String fixURI(String str) {
591:
592: // handle platform dependent strings
593: str = str.replace(java.io.File.separatorChar, '/');
594:
595: // Windows fix
596: if (str.length() >= 2) {
597: char ch1 = str.charAt(1);
598: // change "C:blah" to "/C:blah"
599: if (ch1 == ':') {
600: char ch0 = Character.toUpperCase(str.charAt(0));
601: if (ch0 >= 'A' && ch0 <= 'Z') {
602: str = "/" + str;
603: }
604: }
605: // change "//blah" to "file://blah"
606: else if (ch1 == '/' && str.charAt(0) == '/') {
607: str = "file:" + str;
608: }
609: }
610:
611: // done
612: return str;
613:
614: } // fixURI(String):String
615:
616: }
|