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