1/*
2 * reserved comment block
3 * DO NOT REMOVE OR ALTER!
4 */
5/*
6 * Licensed to the Apache Software Foundation (ASF) under one or more
7 * contributor license agreements.  See the NOTICE file distributed with
8 * this work for additional information regarding copyright ownership.
9 * The ASF licenses this file to You under the Apache License, Version 2.0
10 * (the "License"); you may not use this file except in compliance with
11 * the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 */
21
22package com.sun.org.apache.xerces.internal.jaxp.validation;
23
24import java.lang.ref.Reference;
25import java.lang.ref.ReferenceQueue;
26import java.lang.ref.SoftReference;
27
28import com.sun.org.apache.xerces.internal.xni.grammars.Grammar;
29import com.sun.org.apache.xerces.internal.xni.grammars.XMLGrammarDescription;
30import com.sun.org.apache.xerces.internal.xni.grammars.XMLSchemaDescription;
31import com.sun.org.apache.xerces.internal.xni.grammars.XMLGrammarPool;
32
33/**
34 * <p>This grammar pool is a memory sensitive cache. The grammars
35 * stored in the pool are softly reachable and may be cleared by
36 * the garbage collector in response to memory demand. Equality
37 * of <code>XMLSchemaDescription</code>s is determined using both
38 * the target namespace for the schema and schema location.</p>
39 *
40 * @author Michael Glavassevich, IBM
41 */
42final class SoftReferenceGrammarPool implements XMLGrammarPool {
43
44    //
45    // Constants
46    //
47
48    /** Default size. */
49    protected static final int TABLE_SIZE = 11;
50
51    /** Zero length grammar array. */
52    protected static final Grammar [] ZERO_LENGTH_GRAMMAR_ARRAY = new Grammar [0];
53
54    //
55    // Data
56    //
57
58    /** Grammars. */
59    protected Entry [] fGrammars = null;
60
61    /** Flag indicating whether this pool is locked */
62    protected boolean fPoolIsLocked;
63
64    /** The number of grammars in the pool */
65    protected int fGrammarCount = 0;
66
67    /** Reference queue for cleared grammar references */
68    protected final ReferenceQueue fReferenceQueue = new ReferenceQueue();
69
70    //
71    // Constructors
72    //
73
74    /** Constructs a grammar pool with a default number of buckets. */
75    public SoftReferenceGrammarPool() {
76        fGrammars = new Entry[TABLE_SIZE];
77        fPoolIsLocked = false;
78    } // <init>()
79
80    /** Constructs a grammar pool with a specified number of buckets. */
81    public SoftReferenceGrammarPool(int initialCapacity) {
82        fGrammars = new Entry[initialCapacity];
83        fPoolIsLocked = false;
84    }
85
86    //
87    // XMLGrammarPool methods
88    //
89
90    /* <p> Retrieve the initial known set of grammars. This method is
91     * called by a validator before the validation starts. The application
92     * can provide an initial set of grammars available to the current
93     * validation attempt. </p>
94     *
95     * @param grammarType The type of the grammar, from the
96     *                    <code>com.sun.org.apache.xerces.internal.xni.grammars.XMLGrammarDescription</code>
97     *                    interface.
98     * @return            The set of grammars the validator may put in its "bucket"
99     */
100    public Grammar [] retrieveInitialGrammarSet (String grammarType) {
101        synchronized (fGrammars) {
102            clean();
103            // Return no grammars. This allows the garbage collector to sift
104            // out grammars which are not in use when memory demand is high.
105            // It also allows the pool to return the "right" schema grammar
106            // based on schema locations.
107            return ZERO_LENGTH_GRAMMAR_ARRAY;
108        }
109    } // retrieveInitialGrammarSet (String): Grammar[]
110
111    /* <p> Return the final set of grammars that the validator ended up
112     * with. This method is called after the validation finishes. The
113     * application may then choose to cache some of the returned grammars.</p>
114     * <p>In this implementation, we make our choice based on whether this object
115     * is "locked"--that is, whether the application has instructed
116     * us not to accept any new grammars.</p>
117     *
118     * @param grammarType The type of the grammars being returned;
119     * @param grammars    An array containing the set of grammars being
120     *                    returned; order is not significant.
121     */
122    public void cacheGrammars(String grammarType, Grammar[] grammars) {
123        if (!fPoolIsLocked) {
124            for (int i = 0; i < grammars.length; ++i) {
125                putGrammar(grammars[i]);
126            }
127        }
128    } // cacheGrammars(String, Grammar[]);
129
130    /* <p> This method requests that the application retrieve a grammar
131     * corresponding to the given GrammarIdentifier from its cache.
132     * If it cannot do so it must return null; the parser will then
133     * call the EntityResolver. </p>
134     * <strong>An application must not call its EntityResolver itself
135     * from this method; this may result in infinite recursions.</strong>
136     *
137     * This implementation chooses to use the root element name to identify a DTD grammar
138     * and the target namespace to identify a Schema grammar.
139     *
140     * @param desc The description of the Grammar being requested.
141     * @return     The Grammar corresponding to this description or null if
142     *             no such Grammar is known.
143     */
144    public Grammar retrieveGrammar(XMLGrammarDescription desc) {
145        return getGrammar(desc);
146    } // retrieveGrammar(XMLGrammarDescription):  Grammar
147
148    //
149    // Public methods
150    //
151
152    /**
153     * Puts the specified grammar into the grammar pool and associates it to
154     * its root element name or its target namespace.
155     *
156     * @param grammar The Grammar.
157     */
158    public void putGrammar(Grammar grammar) {
159        if (!fPoolIsLocked) {
160            synchronized (fGrammars) {
161                clean();
162                XMLGrammarDescription desc = grammar.getGrammarDescription();
163                int hash = hashCode(desc);
164                int index = (hash & 0x7FFFFFFF) % fGrammars.length;
165                for (Entry entry = fGrammars[index]; entry != null; entry = entry.next) {
166                    if (entry.hash == hash && equals(entry.desc, desc)) {
167                        if (entry.grammar.get() != grammar) {
168                            entry.grammar = new SoftGrammarReference(entry, grammar, fReferenceQueue);
169                        }
170                        return;
171                    }
172                }
173                // create a new entry
174                Entry entry = new Entry(hash, index, desc, grammar, 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                }
199                else if ((entry.hash == hash) && 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                }
249                else if ((entry.hash == hash) && 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, XMLGrammarDescription desc2) {
296        if (desc1 instanceof XMLSchemaDescription) {
297            if (!(desc2 instanceof XMLSchemaDescription)) {
298                return false;
299            }
300            final XMLSchemaDescription sd1 = (XMLSchemaDescription) desc1;
301            final XMLSchemaDescription sd2 = (XMLSchemaDescription) desc2;
302            final String targetNamespace = sd1.getTargetNamespace();
303            if (targetNamespace != null) {
304                if (!targetNamespace.equals(sd2.getTargetNamespace())) {
305                    return false;
306                }
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            }
322            else if (sd2.getExpandedSystemId() != null) {
323                return false;
324            }
325            return true;
326        }
327        return desc1.equals(desc2);
328    }
329
330    /**
331     * Returns the hash code value for the given grammar description.
332     *
333     * @param desc The grammar description
334     * @return     The hash code value
335     */
336    public int hashCode(XMLGrammarDescription desc) {
337        if (desc instanceof XMLSchemaDescription) {
338            final XMLSchemaDescription sd = (XMLSchemaDescription) desc;
339            final String targetNamespace = sd.getTargetNamespace();
340            final String expandedSystemId = sd.getExpandedSystemId();
341            int hash = (targetNamespace != null) ? targetNamespace.hashCode() : 0;
342            hash ^= (expandedSystemId != null) ? expandedSystemId.hashCode() : 0;
343            return hash;
344        }
345        return desc.hashCode();
346    }
347
348    /**
349     * Removes the given entry from the pool
350     *
351     * @param entry the entry to remove
352     * @return The grammar attached to this entry
353     */
354    private Grammar removeEntry(Entry entry) {
355        if (entry.prev != null) {
356            entry.prev.next = entry.next;
357        }
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, XMLGrammarDescription desc, Grammar grammar, Entry next, ReferenceQueue queue) {
397            this.hash = hash;
398            this.bucket = bucket;
399            this.prev = null;
400            this.next = next;
401            if (next != null) {
402                next.prev = this;
403            }
404            this.desc = desc;
405            this.grammar = new SoftGrammarReference(this, grammar, queue);
406        }
407
408        // clear this entry; useful to promote garbage collection
409        // since reduces reference count of objects to be destroyed
410        protected void clear () {
411            desc = null;
412            grammar = null;
413            if(next != null) {
414                next.clear();
415                next = null;
416            }
417        } // clear()
418
419    } // class Entry
420
421    /**
422     * This class stores a soft reference to a grammar object. It keeps a reference
423     * to its associated entry, so that it can be easily removed from the pool.
424     */
425    static final class SoftGrammarReference extends SoftReference {
426
427        public Entry entry;
428
429        protected SoftGrammarReference(Entry entry, Grammar grammar, ReferenceQueue queue) {
430            super(grammar, queue);
431            this.entry = entry;
432        }
433
434    } // class SoftGrammarReference
435
436} // class SoftReferenceGrammarPool
437