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 
  22 package com.sun.org.apache.xerces.internal.jaxp.validation;
  23 
  24 import java.lang.ref.Reference;
  25 import java.lang.ref.ReferenceQueue;
  26 import java.lang.ref.SoftReference;
  27 
  28 import com.sun.org.apache.xerces.internal.xni.grammars.Grammar;
  29 import com.sun.org.apache.xerces.internal.xni.grammars.XMLGrammarDescription;
  30 import com.sun.org.apache.xerces.internal.xni.grammars.XMLSchemaDescription;
  31 import 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  */
  42 final 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