blob: 2eeb5534f59d92c136d4031bd43ed38b8e349949 [file] [log] [blame]
/*
* 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