001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.xerces.jaxp.validation;
019:
020: import java.lang.ref.Reference;
021: import java.lang.ref.ReferenceQueue;
022: import java.lang.ref.SoftReference;
023:
024: import org.apache.xerces.xni.grammars.Grammar;
025: import org.apache.xerces.xni.grammars.XMLGrammarDescription;
026: import org.apache.xerces.xni.grammars.XMLSchemaDescription;
027: import org.apache.xerces.xni.grammars.XMLGrammarPool;
028:
029: /**
030: * <p>This grammar pool is a memory sensitive cache. The grammars
031: * stored in the pool are softly reachable and may be cleared by
032: * the garbage collector in response to memory demand. Equality
033: * of <code>XMLSchemaDescription</code>s is determined using both
034: * the target namespace for the schema and schema location.</p>
035: *
036: * @author Michael Glavassevich, IBM
037: * @version $Id: SoftReferenceGrammarPool.java 447235 2006-09-18 05:01:44Z mrglavas $
038: */
039: final class SoftReferenceGrammarPool implements XMLGrammarPool {
040:
041: //
042: // Constants
043: //
044:
045: /** Default size. */
046: protected static final int TABLE_SIZE = 11;
047:
048: /** Zero length grammar array. */
049: protected static final Grammar[] ZERO_LENGTH_GRAMMAR_ARRAY = new Grammar[0];
050:
051: //
052: // Data
053: //
054:
055: /** Grammars. */
056: protected Entry[] fGrammars = null;
057:
058: /** Flag indicating whether this pool is locked */
059: protected boolean fPoolIsLocked;
060:
061: /** The number of grammars in the pool */
062: protected int fGrammarCount = 0;
063:
064: /** Reference queue for cleared grammar references */
065: protected final ReferenceQueue fReferenceQueue = new ReferenceQueue();
066:
067: //
068: // Constructors
069: //
070:
071: /** Constructs a grammar pool with a default number of buckets. */
072: public SoftReferenceGrammarPool() {
073: fGrammars = new Entry[TABLE_SIZE];
074: fPoolIsLocked = false;
075: } // <init>()
076:
077: /** Constructs a grammar pool with a specified number of buckets. */
078: public SoftReferenceGrammarPool(int initialCapacity) {
079: fGrammars = new Entry[initialCapacity];
080: fPoolIsLocked = false;
081: }
082:
083: //
084: // XMLGrammarPool methods
085: //
086:
087: /* <p> Retrieve the initial known set of grammars. This method is
088: * called by a validator before the validation starts. The application
089: * can provide an initial set of grammars available to the current
090: * validation attempt. </p>
091: *
092: * @param grammarType The type of the grammar, from the
093: * <code>org.apache.xerces.xni.grammars.XMLGrammarDescription</code>
094: * interface.
095: * @return The set of grammars the validator may put in its "bucket"
096: */
097: public Grammar[] retrieveInitialGrammarSet(String grammarType) {
098: synchronized (fGrammars) {
099: clean();
100: // Return no grammars. This allows the garbage collector to sift
101: // out grammars which are not in use when memory demand is high.
102: // It also allows the pool to return the "right" schema grammar
103: // based on schema locations.
104: return ZERO_LENGTH_GRAMMAR_ARRAY;
105: }
106: } // retrieveInitialGrammarSet (String): Grammar[]
107:
108: /* <p> Return the final set of grammars that the validator ended up
109: * with. This method is called after the validation finishes. The
110: * application may then choose to cache some of the returned grammars.</p>
111: * <p>In this implementation, we make our choice based on whether this object
112: * is "locked"--that is, whether the application has instructed
113: * us not to accept any new grammars.</p>
114: *
115: * @param grammarType The type of the grammars being returned;
116: * @param grammars An array containing the set of grammars being
117: * returned; order is not significant.
118: */
119: public void cacheGrammars(String grammarType, Grammar[] grammars) {
120: if (!fPoolIsLocked) {
121: for (int i = 0; i < grammars.length; ++i) {
122: putGrammar(grammars[i]);
123: }
124: }
125: } // cacheGrammars(String, Grammar[]);
126:
127: /* <p> This method requests that the application retrieve a grammar
128: * corresponding to the given GrammarIdentifier from its cache.
129: * If it cannot do so it must return null; the parser will then
130: * call the EntityResolver. </p>
131: * <strong>An application must not call its EntityResolver itself
132: * from this method; this may result in infinite recursions.</strong>
133: *
134: * This implementation chooses to use the root element name to identify a DTD grammar
135: * and the target namespace to identify a Schema grammar.
136: *
137: * @param desc The description of the Grammar being requested.
138: * @return The Grammar corresponding to this description or null if
139: * no such Grammar is known.
140: */
141: public Grammar retrieveGrammar(XMLGrammarDescription desc) {
142: return getGrammar(desc);
143: } // retrieveGrammar(XMLGrammarDescription): Grammar
144:
145: //
146: // Public methods
147: //
148:
149: /**
150: * Puts the specified grammar into the grammar pool and associates it to
151: * its root element name or its target namespace.
152: *
153: * @param grammar The Grammar.
154: */
155: public void putGrammar(Grammar grammar) {
156: if (!fPoolIsLocked) {
157: synchronized (fGrammars) {
158: clean();
159: XMLGrammarDescription desc = grammar
160: .getGrammarDescription();
161: int hash = hashCode(desc);
162: int index = (hash & 0x7FFFFFFF) % fGrammars.length;
163: for (Entry entry = fGrammars[index]; entry != null; entry = entry.next) {
164: if (entry.hash == hash && equals(entry.desc, desc)) {
165: if (entry.grammar.get() != grammar) {
166: entry.grammar = new SoftGrammarReference(
167: entry, grammar, fReferenceQueue);
168: }
169: return;
170: }
171: }
172: // create a new entry
173: Entry entry = new Entry(hash, index, desc, grammar,
174: fGrammars[index], fReferenceQueue);
175: fGrammars[index] = entry;
176: fGrammarCount++;
177: }
178: }
179: } // putGrammar(Grammar)
180:
181: /**
182: * Returns the grammar associated to the specified grammar description.
183: * Currently, the root element name is used as the key for DTD grammars
184: * and the target namespace is used as the key for Schema grammars.
185: *
186: * @param desc The Grammar Description.
187: */
188: public Grammar getGrammar(XMLGrammarDescription desc) {
189: synchronized (fGrammars) {
190: clean();
191: int hash = hashCode(desc);
192: int index = (hash & 0x7FFFFFFF) % fGrammars.length;
193: for (Entry entry = fGrammars[index]; entry != null; entry = entry.next) {
194: Grammar tempGrammar = (Grammar) entry.grammar.get();
195: /** If the soft reference has been cleared, remove this entry from the pool. */
196: if (tempGrammar == null) {
197: removeEntry(entry);
198: } else if ((entry.hash == hash)
199: && equals(entry.desc, desc)) {
200: return tempGrammar;
201: }
202: }
203: return null;
204: }
205: } // getGrammar(XMLGrammarDescription):Grammar
206:
207: /**
208: * Removes the grammar associated to the specified grammar description from the
209: * grammar pool and returns the removed grammar. Currently, the root element name
210: * is used as the key for DTD grammars and the target namespace is used
211: * as the key for Schema grammars.
212: *
213: * @param desc The Grammar Description.
214: * @return The removed grammar.
215: */
216: public Grammar removeGrammar(XMLGrammarDescription desc) {
217: synchronized (fGrammars) {
218: clean();
219: int hash = hashCode(desc);
220: int index = (hash & 0x7FFFFFFF) % fGrammars.length;
221: for (Entry entry = fGrammars[index]; entry != null; entry = entry.next) {
222: if ((entry.hash == hash) && equals(entry.desc, desc)) {
223: return removeEntry(entry);
224: }
225: }
226: return null;
227: }
228: } // removeGrammar(XMLGrammarDescription):Grammar
229:
230: /**
231: * Returns true if the grammar pool contains a grammar associated
232: * to the specified grammar description. Currently, the root element name
233: * is used as the key for DTD grammars and the target namespace is used
234: * as the key for Schema grammars.
235: *
236: * @param desc The Grammar Description.
237: */
238: public boolean containsGrammar(XMLGrammarDescription desc) {
239: synchronized (fGrammars) {
240: clean();
241: int hash = hashCode(desc);
242: int index = (hash & 0x7FFFFFFF) % fGrammars.length;
243: for (Entry entry = fGrammars[index]; entry != null; entry = entry.next) {
244: Grammar tempGrammar = (Grammar) entry.grammar.get();
245: /** If the soft reference has been cleared, remove this entry from the pool. */
246: if (tempGrammar == null) {
247: removeEntry(entry);
248: } else if ((entry.hash == hash)
249: && equals(entry.desc, desc)) {
250: return true;
251: }
252: }
253: return false;
254: }
255: } // containsGrammar(XMLGrammarDescription):boolean
256:
257: /* <p> Sets this grammar pool to a "locked" state--i.e.,
258: * no new grammars will be added until it is "unlocked".
259: */
260: public void lockPool() {
261: fPoolIsLocked = true;
262: } // lockPool()
263:
264: /* <p> Sets this grammar pool to an "unlocked" state--i.e.,
265: * new grammars will be added when putGrammar or cacheGrammars
266: * are called.
267: */
268: public void unlockPool() {
269: fPoolIsLocked = false;
270: } // unlockPool()
271:
272: /*
273: * <p>This method clears the pool-i.e., removes references
274: * to all the grammars in it.</p>
275: */
276: public void clear() {
277: for (int i = 0; i < fGrammars.length; i++) {
278: if (fGrammars[i] != null) {
279: fGrammars[i].clear();
280: fGrammars[i] = null;
281: }
282: }
283: fGrammarCount = 0;
284: } // clear()
285:
286: /**
287: * This method checks whether two grammars are the same. Currently, we compare
288: * the root element names for DTD grammars and the target namespaces for Schema grammars.
289: * The application can override this behaviour and add its own logic.
290: *
291: * @param desc1 The grammar description
292: * @param desc2 The grammar description of the grammar to be compared to
293: * @return True if the grammars are equal, otherwise false
294: */
295: public boolean equals(XMLGrammarDescription desc1,
296: XMLGrammarDescription desc2) {
297: if (desc1 instanceof XMLSchemaDescription) {
298: if (!(desc2 instanceof XMLSchemaDescription)) {
299: return false;
300: }
301: final XMLSchemaDescription sd1 = (XMLSchemaDescription) desc1;
302: final XMLSchemaDescription sd2 = (XMLSchemaDescription) desc2;
303: final String targetNamespace = sd1.getTargetNamespace();
304: if (targetNamespace != null) {
305: if (!targetNamespace.equals(sd2.getTargetNamespace())) {
306: return false;
307: }
308: } else if (sd2.getTargetNamespace() != null) {
309: return false;
310: }
311: // The JAXP 1.3 spec says that the implementation can assume that
312: // if two schema location hints are the same they always resolve
313: // to the same document. In the default grammar pool implementation
314: // we only look at the target namespaces. Here we also compare
315: // location hints.
316: final String expandedSystemId = sd1.getExpandedSystemId();
317: if (expandedSystemId != null) {
318: if (!expandedSystemId.equals(sd2.getExpandedSystemId())) {
319: return false;
320: }
321: } else if (sd2.getExpandedSystemId() != null) {
322: return false;
323: }
324: return true;
325: }
326: return desc1.equals(desc2);
327: }
328:
329: /**
330: * Returns the hash code value for the given grammar description.
331: *
332: * @param desc The grammar description
333: * @return The hash code value
334: */
335: public int hashCode(XMLGrammarDescription desc) {
336: if (desc instanceof XMLSchemaDescription) {
337: final XMLSchemaDescription sd = (XMLSchemaDescription) desc;
338: final String targetNamespace = sd.getTargetNamespace();
339: final String expandedSystemId = sd.getExpandedSystemId();
340: int hash = (targetNamespace != null) ? targetNamespace
341: .hashCode() : 0;
342: hash ^= (expandedSystemId != null) ? expandedSystemId
343: .hashCode() : 0;
344: return hash;
345: }
346: return desc.hashCode();
347: }
348:
349: /**
350: * Removes the given entry from the pool
351: *
352: * @param entry the entry to remove
353: * @return The grammar attached to this entry
354: */
355: private Grammar removeEntry(Entry entry) {
356: if (entry.prev != null) {
357: entry.prev.next = entry.next;
358: } else {
359: fGrammars[entry.bucket] = entry.next;
360: }
361: if (entry.next != null) {
362: entry.next.prev = entry.prev;
363: }
364: --fGrammarCount;
365: entry.grammar.entry = null;
366: return (Grammar) entry.grammar.get();
367: }
368:
369: /**
370: * Removes stale entries from the pool.
371: */
372: private void clean() {
373: Reference ref = fReferenceQueue.poll();
374: while (ref != null) {
375: Entry entry = ((SoftGrammarReference) ref).entry;
376: if (entry != null) {
377: removeEntry(entry);
378: }
379: ref = fReferenceQueue.poll();
380: }
381: }
382:
383: /**
384: * This class is a grammar pool entry. Each entry acts as a node
385: * in a doubly linked list.
386: */
387: static final class Entry {
388:
389: public int hash;
390: public int bucket;
391: public Entry prev;
392: public Entry next;
393: public XMLGrammarDescription desc;
394: public SoftGrammarReference grammar;
395:
396: protected Entry(int hash, int bucket,
397: XMLGrammarDescription desc, Grammar grammar,
398: Entry next, ReferenceQueue queue) {
399: this .hash = hash;
400: this .bucket = bucket;
401: this .prev = null;
402: this .next = next;
403: if (next != null) {
404: next.prev = this ;
405: }
406: this .desc = desc;
407: this .grammar = new SoftGrammarReference(this , grammar,
408: queue);
409: }
410:
411: // clear this entry; useful to promote garbage collection
412: // since reduces reference count of objects to be destroyed
413: protected void clear() {
414: desc = null;
415: grammar = null;
416: if (next != null) {
417: next.clear();
418: next = null;
419: }
420: } // clear()
421:
422: } // class Entry
423:
424: /**
425: * This class stores a soft reference to a grammar object. It keeps a reference
426: * to its associated entry, so that it can be easily removed from the pool.
427: */
428: static final class SoftGrammarReference extends SoftReference {
429:
430: public Entry entry;
431:
432: protected SoftGrammarReference(Entry entry, Grammar grammar,
433: ReferenceQueue queue) {
434: super (grammar, queue);
435: this .entry = entry;
436: }
437:
438: } // class SoftGrammarReference
439:
440: } // class SoftReferenceGrammarPool
|