| /* |
| * reserved comment block |
| * DO NOT REMOVE OR ALTER! |
| */ |
| /* |
| * Copyright 2005 The Apache Software Foundation. |
| * |
| * Licensed 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 com.sun.org.apache.xerces.internal.jaxp.validation; |
| |
| import java.lang.ref.Reference; |
| import java.lang.ref.ReferenceQueue; |
| import java.lang.ref.SoftReference; |
| |
| import com.sun.org.apache.xerces.internal.xni.grammars.Grammar; |
| import com.sun.org.apache.xerces.internal.xni.grammars.XMLGrammarDescription; |
| import com.sun.org.apache.xerces.internal.xni.grammars.XMLSchemaDescription; |
| import com.sun.org.apache.xerces.internal.xni.grammars.XMLGrammarPool; |
| |
| /** |
| * <p>This grammar pool is a memory sensitive cache. The grammars |
| * stored in the pool are softly reachable and may be cleared by |
| * the garbage collector in response to memory demand. Equality |
| * of <code>XMLSchemaDescription</code>s is determined using both |
| * the target namespace for the schema and schema location.</p> |
| * |
| * @author Michael Glavassevich, IBM |
| */ |
| final class SoftReferenceGrammarPool implements XMLGrammarPool { |
| |
| // |
| // Constants |
| // |
| |
| /** Default size. */ |
| protected static final int TABLE_SIZE = 11; |
| |
| /** Zero length grammar array. */ |
| protected static final Grammar [] ZERO_LENGTH_GRAMMAR_ARRAY = new Grammar [0]; |
| |
| // |
| // Data |
| // |
| |
| /** Grammars. */ |
| protected Entry [] fGrammars = null; |
| |
| /** Flag indicating whether this pool is locked */ |
| protected boolean fPoolIsLocked; |
| |
| /** The number of grammars in the pool */ |
| protected int fGrammarCount = 0; |
| |
| /** Reference queue for cleared grammar references */ |
| protected final ReferenceQueue fReferenceQueue = new ReferenceQueue(); |
| |
| // |
| // Constructors |
| // |
| |
| /** Constructs a grammar pool with a default number of buckets. */ |
| public SoftReferenceGrammarPool() { |
| fGrammars = new Entry[TABLE_SIZE]; |
| fPoolIsLocked = false; |
| } // <init>() |
| |
| /** Constructs a grammar pool with a specified number of buckets. */ |
| public SoftReferenceGrammarPool(int initialCapacity) { |
| fGrammars = new Entry[initialCapacity]; |
| fPoolIsLocked = false; |
| } |
| |
| // |
| // XMLGrammarPool methods |
| // |
| |
| /* <p> Retrieve the initial known set of grammars. This method is |
| * called by a validator before the validation starts. The application |
| * can provide an initial set of grammars available to the current |
| * validation attempt. </p> |
| * |
| * @param grammarType The type of the grammar, from the |
| * <code>com.sun.org.apache.xerces.internal.xni.grammars.XMLGrammarDescription</code> |
| * interface. |
| * @return The set of grammars the validator may put in its "bucket" |
| */ |
| public Grammar [] retrieveInitialGrammarSet (String grammarType) { |
| synchronized (fGrammars) { |
| clean(); |
| // Return no grammars. This allows the garbage collector to sift |
| // out grammars which are not in use when memory demand is high. |
| // It also allows the pool to return the "right" schema grammar |
| // based on schema locations. |
| return ZERO_LENGTH_GRAMMAR_ARRAY; |
| } |
| } // retrieveInitialGrammarSet (String): Grammar[] |
| |
| /* <p> Return the final set of grammars that the validator ended up |
| * with. This method is called after the validation finishes. The |
| * application may then choose to cache some of the returned grammars.</p> |
| * <p>In this implementation, we make our choice based on whether this object |
| * is "locked"--that is, whether the application has instructed |
| * us not to accept any new grammars.</p> |
| * |
| * @param grammarType The type of the grammars being returned; |
| * @param grammars An array containing the set of grammars being |
| * returned; order is not significant. |
| */ |
| public void cacheGrammars(String grammarType, Grammar[] grammars) { |
| if (!fPoolIsLocked) { |
| for (int i = 0; i < grammars.length; ++i) { |
| putGrammar(grammars[i]); |
| } |
| } |
| } // cacheGrammars(String, Grammar[]); |
| |
| /* <p> This method requests that the application retrieve a grammar |
| * corresponding to the given GrammarIdentifier from its cache. |
| * If it cannot do so it must return null; the parser will then |
| * call the EntityResolver. </p> |
| * <strong>An application must not call its EntityResolver itself |
| * from this method; this may result in infinite recursions.</strong> |
| * |
| * This implementation chooses to use the root element name to identify a DTD grammar |
| * and the target namespace to identify a Schema grammar. |
| * |
| * @param desc The description of the Grammar being requested. |
| * @return The Grammar corresponding to this description or null if |
| * no such Grammar is known. |
| */ |
| public Grammar retrieveGrammar(XMLGrammarDescription desc) { |
| return getGrammar(desc); |
| } // retrieveGrammar(XMLGrammarDescription): Grammar |
| |
| // |
| // Public methods |
| // |
| |
| /** |
| * Puts the specified grammar into the grammar pool and associates it to |
| * its root element name or its target namespace. |
| * |
| * @param grammar The Grammar. |
| */ |
| public void putGrammar(Grammar grammar) { |
| if (!fPoolIsLocked) { |
| synchronized (fGrammars) { |
| clean(); |
| XMLGrammarDescription desc = grammar.getGrammarDescription(); |
| int hash = hashCode(desc); |
| int index = (hash & 0x7FFFFFFF) % fGrammars.length; |
| for (Entry entry = fGrammars[index]; entry != null; entry = entry.next) { |
| if (entry.hash == hash && equals(entry.desc, desc)) { |
| if (entry.grammar.get() != grammar) { |
| entry.grammar = new SoftGrammarReference(entry, grammar, fReferenceQueue); |
| } |
| return; |
| } |
| } |
| // create a new entry |
| Entry entry = new Entry(hash, index, desc, grammar, fGrammars[index], fReferenceQueue); |
| fGrammars[index] = entry; |
| fGrammarCount++; |
| } |
| } |
| } // putGrammar(Grammar) |
| |
| /** |
| * Returns the grammar associated to the specified grammar description. |
| * Currently, the root element name is used as the key for DTD grammars |
| * and the target namespace is used as the key for Schema grammars. |
| * |
| * @param desc The Grammar Description. |
| */ |
| public Grammar getGrammar(XMLGrammarDescription desc) { |
| synchronized (fGrammars) { |
| clean(); |
| int hash = hashCode(desc); |
| int index = (hash & 0x7FFFFFFF) % fGrammars.length; |
| for (Entry entry = fGrammars[index]; entry != null; entry = entry.next) { |
| Grammar tempGrammar = (Grammar) entry.grammar.get(); |
| /** If the soft reference has been cleared, remove this entry from the pool. */ |
| if (tempGrammar == null) { |
| removeEntry(entry); |
| } |
| else if ((entry.hash == hash) && equals(entry.desc, desc)) { |
| return tempGrammar; |
| } |
| } |
| return null; |
| } |
| } // getGrammar(XMLGrammarDescription):Grammar |
| |
| /** |
| * Removes the grammar associated to the specified grammar description from the |
| * grammar pool and returns the removed grammar. Currently, the root element name |
| * is used as the key for DTD grammars and the target namespace is used |
| * as the key for Schema grammars. |
| * |
| * @param desc The Grammar Description. |
| * @return The removed grammar. |
| */ |
| public Grammar removeGrammar(XMLGrammarDescription desc) { |
| synchronized (fGrammars) { |
| clean(); |
| int hash = hashCode(desc); |
| int index = (hash & 0x7FFFFFFF) % fGrammars.length; |
| for (Entry entry = fGrammars[index]; entry != null; entry = entry.next) { |
| if ((entry.hash == hash) && equals(entry.desc, desc)) { |
| return removeEntry(entry); |
| } |
| } |
| return null; |
| } |
| } // removeGrammar(XMLGrammarDescription):Grammar |
| |
| /** |
| * Returns true if the grammar pool contains a grammar associated |
| * to the specified grammar description. Currently, the root element name |
| * is used as the key for DTD grammars and the target namespace is used |
| * as the key for Schema grammars. |
| * |
| * @param desc The Grammar Description. |
| */ |
| public boolean containsGrammar(XMLGrammarDescription desc) { |
| synchronized (fGrammars) { |
| clean(); |
| int hash = hashCode(desc); |
| int index = (hash & 0x7FFFFFFF) % fGrammars.length; |
| for (Entry entry = fGrammars[index]; entry != null ; entry = entry.next) { |
| Grammar tempGrammar = (Grammar) entry.grammar.get(); |
| /** If the soft reference has been cleared, remove this entry from the pool. */ |
| if (tempGrammar == null) { |
| removeEntry(entry); |
| } |
| else if ((entry.hash == hash) && equals(entry.desc, desc)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| } // containsGrammar(XMLGrammarDescription):boolean |
| |
| /* <p> Sets this grammar pool to a "locked" state--i.e., |
| * no new grammars will be added until it is "unlocked". |
| */ |
| public void lockPool() { |
| fPoolIsLocked = true; |
| } // lockPool() |
| |
| /* <p> Sets this grammar pool to an "unlocked" state--i.e., |
| * new grammars will be added when putGrammar or cacheGrammars |
| * are called. |
| */ |
| public void unlockPool() { |
| fPoolIsLocked = false; |
| } // unlockPool() |
| |
| /* |
| * <p>This method clears the pool-i.e., removes references |
| * to all the grammars in it.</p> |
| */ |
| public void clear() { |
| for (int i=0; i<fGrammars.length; i++) { |
| if(fGrammars[i] != null) { |
| fGrammars[i].clear(); |
| fGrammars[i] = null; |
| } |
| } |
| fGrammarCount = 0; |
| } // clear() |
| |
| /** |
| * This method checks whether two grammars are the same. Currently, we compare |
| * the root element names for DTD grammars and the target namespaces for Schema grammars. |
| * The application can override this behaviour and add its own logic. |
| * |
| * @param desc1 The grammar description |
| * @param desc2 The grammar description of the grammar to be compared to |
| * @return True if the grammars are equal, otherwise false |
| */ |
| public boolean equals(XMLGrammarDescription desc1, XMLGrammarDescription desc2) { |
| if (desc1 instanceof XMLSchemaDescription) { |
| if (!(desc2 instanceof XMLSchemaDescription)) { |
| return false; |
| } |
| final XMLSchemaDescription sd1 = (XMLSchemaDescription) desc1; |
| final XMLSchemaDescription sd2 = (XMLSchemaDescription) desc2; |
| final String targetNamespace = sd1.getTargetNamespace(); |
| if (targetNamespace != null) { |
| if (!targetNamespace.equals(sd2.getTargetNamespace())) { |
| return false; |
| } |
| } |
| else if (sd2.getTargetNamespace() != null) { |
| return false; |
| } |
| // The JAXP 1.3 spec says that the implementation can assume that |
| // if two schema location hints are the same they always resolve |
| // to the same document. In the default grammar pool implementation |
| // we only look at the target namespaces. Here we also compare |
| // location hints. |
| final String expandedSystemId = sd1.getExpandedSystemId(); |
| if (expandedSystemId != null) { |
| if (!expandedSystemId.equals(sd2.getExpandedSystemId())) { |
| return false; |
| } |
| } |
| else if (sd2.getExpandedSystemId() != null) { |
| return false; |
| } |
| return true; |
| } |
| return desc1.equals(desc2); |
| } |
| |
| /** |
| * Returns the hash code value for the given grammar description. |
| * |
| * @param desc The grammar description |
| * @return The hash code value |
| */ |
| public int hashCode(XMLGrammarDescription desc) { |
| if (desc instanceof XMLSchemaDescription) { |
| final XMLSchemaDescription sd = (XMLSchemaDescription) desc; |
| final String targetNamespace = sd.getTargetNamespace(); |
| final String expandedSystemId = sd.getExpandedSystemId(); |
| int hash = (targetNamespace != null) ? targetNamespace.hashCode() : 0; |
| hash ^= (expandedSystemId != null) ? expandedSystemId.hashCode() : 0; |
| return hash; |
| } |
| return desc.hashCode(); |
| } |
| |
| /** |
| * Removes the given entry from the pool |
| * |
| * @param entry the entry to remove |
| * @return The grammar attached to this entry |
| */ |
| private Grammar removeEntry(Entry entry) { |
| if (entry.prev != null) { |
| entry.prev.next = entry.next; |
| } |
| else { |
| fGrammars[entry.bucket] = entry.next; |
| } |
| if (entry.next != null) { |
| entry.next.prev = entry.prev; |
| } |
| --fGrammarCount; |
| entry.grammar.entry = null; |
| return (Grammar) entry.grammar.get(); |
| } |
| |
| /** |
| * Removes stale entries from the pool. |
| */ |
| private void clean() { |
| Reference ref = fReferenceQueue.poll(); |
| while (ref != null) { |
| Entry entry = ((SoftGrammarReference) ref).entry; |
| if (entry != null) { |
| removeEntry(entry); |
| } |
| ref = fReferenceQueue.poll(); |
| } |
| } |
| |
| /** |
| * This class is a grammar pool entry. Each entry acts as a node |
| * in a doubly linked list. |
| */ |
| static final class Entry { |
| |
| public int hash; |
| public int bucket; |
| public Entry prev; |
| public Entry next; |
| public XMLGrammarDescription desc; |
| public SoftGrammarReference grammar; |
| |
| protected Entry(int hash, int bucket, XMLGrammarDescription desc, Grammar grammar, Entry next, ReferenceQueue queue) { |
| this.hash = hash; |
| this.bucket = bucket; |
| this.prev = null; |
| this.next = next; |
| if (next != null) { |
| next.prev = this; |
| } |
| this.desc = desc; |
| this.grammar = new SoftGrammarReference(this, grammar, queue); |
| } |
| |
| // clear this entry; useful to promote garbage collection |
| // since reduces reference count of objects to be destroyed |
| protected void clear () { |
| desc = null; |
| grammar = null; |
| if(next != null) { |
| next.clear(); |
| next = null; |
| } |
| } // clear() |
| |
| } // class Entry |
| |
| /** |
| * This class stores a soft reference to a grammar object. It keeps a reference |
| * to its associated entry, so that it can be easily removed from the pool. |
| */ |
| static final class SoftGrammarReference extends SoftReference { |
| |
| public Entry entry; |
| |
| protected SoftGrammarReference(Entry entry, Grammar grammar, ReferenceQueue queue) { |
| super(grammar, queue); |
| this.entry = entry; |
| } |
| |
| } // class SoftGrammarReference |
| |
| } // class SoftReferenceGrammarPool |