CatalogImpl.java revision 862:b34427de0b08
1178476Sjb/* 2178476Sjb * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. 3178476Sjb * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4178476Sjb * 5178476Sjb * This code is free software; you can redistribute it and/or modify it 6178476Sjb * under the terms of the GNU General Public License version 2 only, as 7178476Sjb * published by the Free Software Foundation. Oracle designates this 8178476Sjb * particular file as subject to the "Classpath" exception as provided 9178476Sjb * by Oracle in the LICENSE file that accompanied this code. 10178476Sjb * 11178476Sjb * This code is distributed in the hope that it will be useful, but WITHOUT 12178476Sjb * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13178476Sjb * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14178476Sjb * version 2 for more details (a copy is included in the LICENSE file that 15178476Sjb * accompanied this code). 16178476Sjb * 17178476Sjb * You should have received a copy of the GNU General Public License version 18178476Sjb * 2 along with this work; if not, write to the Free Software Foundation, 19178476Sjb * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20178476Sjb * 21178476Sjb * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22178476Sjb * or visit www.oracle.com if you need additional information or have any 23178476Sjb * questions. 24178476Sjb */ 25178476Sjbpackage javax.xml.catalog; 26178476Sjb 27178476Sjbimport com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl; 28178476Sjbimport java.io.File; 29178476Sjbimport java.io.IOException; 30178476Sjbimport java.net.MalformedURLException; 31178476Sjbimport java.net.URI; 32178476Sjbimport java.net.URISyntaxException; 33178476Sjbimport java.net.URL; 34178476Sjbimport java.util.ArrayList; 35178476Sjbimport java.util.Iterator; 36178476Sjbimport java.util.List; 37178476Sjbimport java.util.NoSuchElementException; 38178476Sjbimport java.util.Spliterator; 39178476Sjbimport java.util.Spliterators; 40178476Sjbimport java.util.stream.Stream; 41178476Sjbimport java.util.stream.StreamSupport; 42178476Sjbimport static javax.xml.catalog.BaseEntry.CatalogEntryType; 43178476Sjbimport static javax.xml.catalog.CatalogFeatures.DEFER_TRUE; 44178476Sjbimport javax.xml.catalog.CatalogFeatures.Feature; 45178476Sjbimport static javax.xml.catalog.CatalogMessages.formatMessage; 46178476Sjbimport javax.xml.parsers.ParserConfigurationException; 47178476Sjbimport javax.xml.parsers.SAXParser; 48178476Sjbimport javax.xml.parsers.SAXParserFactory; 49178476Sjbimport org.xml.sax.SAXException; 50178476Sjb 51178476Sjb/** 52178476Sjb * Implementation of the Catalog. 53178476Sjb * 54178476Sjb * @since 9 55178476Sjb */ 56178476Sjbclass CatalogImpl extends GroupEntry implements Catalog { 57178476Sjb 58178476Sjb //Catalog level, 0 means the top catalog 59178476Sjb int level = 0; 60178476Sjb 61178476Sjb //Value of the defer attribute to determine if alternative catalogs are read 62178476Sjb boolean isDeferred = true; 63178476Sjb 64178476Sjb //Value of the resolve attribute 65178476Sjb ResolveType resolveType = ResolveType.STRICT; 66178476Sjb 67178476Sjb //indicate whether the Catalog is empty 68178476Sjb boolean isEmpty; 69178476Sjb 70178476Sjb //Current parsed Catalog 71 int current = 0; 72 73 //System Id for this catalog 74 String systemId; 75 76 //The parent 77 CatalogImpl parent = null; 78 79 /* 80 A list of catalog entry files from the input, excluding the current catalog. 81 Paths in the List are normalized. 82 */ 83 List<String> inputFiles; 84 85 //A list of catalogs specified using the nextCatalog element 86 List<NextCatalog> nextCatalogs; 87 88 //reuse the parser 89 SAXParser parser; 90 91 /** 92 * Construct a Catalog with specified path. 93 * 94 * @param file The path to a catalog file. 95 * @throws CatalogException If an error happens while parsing the specified 96 * catalog file. 97 */ 98 public CatalogImpl(CatalogFeatures f, String... file) throws CatalogException { 99 this(null, f, file); 100 } 101 102 /** 103 * Construct a Catalog with specified path. 104 * 105 * @param parent The parent catalog 106 * @param file The path to a catalog file. 107 * @throws CatalogException If an error happens while parsing the specified 108 * catalog file. 109 */ 110 public CatalogImpl(CatalogImpl parent, CatalogFeatures f, String... file) throws CatalogException { 111 super(CatalogEntryType.CATALOG); 112 if (f == null) { 113 throw new NullPointerException( 114 formatMessage(CatalogMessages.ERR_NULL_ARGUMENT, new Object[]{"CatalogFeatures"})); 115 } 116 117 if (file.length > 0) { 118 CatalogMessages.reportNPEOnNull("The path to the catalog file", file[0]); 119 } 120 121 init(parent, f); 122 123 //Path of catalog files 124 String[] catalogFile = file; 125 if (level == 0 && file.length == 0) { 126 String files = features.get(Feature.FILES); 127 if (files != null) { 128 catalogFile = files.split(";[ ]*"); 129 } 130 } 131 132 /* 133 In accordance with 8. Resource Failures of the Catalog spec, missing 134 Catalog entry files are to be ignored. 135 */ 136 if ((catalogFile != null && catalogFile.length > 0)) { 137 int start = 0; 138 URI uri = null; 139 for (String temp : catalogFile) { 140 uri = getSystemId(temp); 141 start++; 142 if (verifyCatalogFile(uri)) { 143 systemId = uri.toASCIIString(); 144 break; 145 } 146 } 147 148 //Save the rest of input files as alternative catalogs 149 if (level == 0 && catalogFile.length > start) { 150 inputFiles = new ArrayList<>(); 151 for (int i = start; i < catalogFile.length; i++) { 152 if (catalogFile[i] != null) { 153 inputFiles.add(catalogFile[i]); 154 } 155 } 156 } 157 158 if (systemId != null) { 159 parse(systemId); 160 } 161 } 162 } 163 164 private void init(CatalogImpl parent, CatalogFeatures f) { 165 this.parent = parent; 166 if (parent == null) { 167 level = 0; 168 } else { 169 level = parent.level + 1; 170 } 171 if (f == null) { 172 this.features = CatalogFeatures.defaults(); 173 } else { 174 this.features = f; 175 } 176 setPrefer(features.get(Feature.PREFER)); 177 setDeferred(features.get(Feature.DEFER)); 178 setResolve(features.get(Feature.RESOLVE)); 179 } 180 181 /** 182 * Resets the Catalog instance to its initial state. 183 */ 184 @Override 185 public void reset() { 186 super.reset(); 187 current = 0; 188 if (level == 0) { 189 catalogsSearched.clear(); 190 } 191 entries.stream().filter((entry) -> (entry.type == CatalogEntryType.GROUP)).forEach((entry) -> { 192 ((GroupEntry) entry).reset(); 193 }); 194 195 if (parent != null) { 196 this.loadedCatalogs = parent.loadedCatalogs; 197 this.catalogsSearched = parent.catalogsSearched; 198 } 199 } 200 201 /** 202 * Returns whether this Catalog instance is the top (main) Catalog. 203 * 204 * @return true if the instance is the top Catalog, false otherwise 205 */ 206 boolean isTop() { 207 return level == 0; 208 } 209 210 /** 211 * Gets the parent of this catalog. 212 * 213 * @returns The parent catalog 214 */ 215 public Catalog getParent() { 216 return this.parent; 217 } 218 219 /** 220 * Sets the defer property. If the value is null or empty, or any String 221 * other than the defined, it will be assumed as the default value. 222 * 223 * @param value The value of the defer attribute 224 */ 225 public final void setDeferred(String value) { 226 isDeferred = DEFER_TRUE.equals(value); 227 } 228 229 /** 230 * Queries the defer attribute 231 * 232 * @return true if the prefer attribute is set to system, false if not. 233 */ 234 public boolean isDeferred() { 235 return isDeferred; 236 } 237 238 /** 239 * Sets the resolve property. If the value is null or empty, or any String 240 * other than the defined, it will be assumed as the default value. 241 * 242 * @param value The value of the resolve attribute 243 */ 244 public final void setResolve(String value) { 245 resolveType = ResolveType.getType(value); 246 } 247 248 /** 249 * Gets the value of the resolve attribute 250 * 251 * @return The value of the resolve attribute 252 */ 253 public final ResolveType getResolve() { 254 return resolveType; 255 } 256 257 /** 258 * Marks the Catalog as being searched already. 259 */ 260 void markAsSearched() { 261 catalogsSearched.add(systemId); 262 } 263 264 /** 265 * Parses the catalog. 266 * 267 * @param systemId The systemId of the catalog 268 * @throws CatalogException if parsing the catalog failed 269 */ 270 private void parse(String systemId) { 271 if (parser == null) { 272 parser = getParser(); 273 } 274 275 try { 276 CatalogReader reader = new CatalogReader(this, parser); 277 parser.parse(systemId, reader); 278 } catch (SAXException | IOException ex) { 279 CatalogMessages.reportRunTimeError(CatalogMessages.ERR_PARSING_FAILED, ex); 280 } 281 } 282 283 /** 284 * Resolves the specified file path to an absolute systemId. If it is 285 * relative, it shall be resolved using the base or user.dir property if 286 * base is not specified. 287 * 288 * @param file The specified file path 289 * @return The systemId of the file 290 * @throws CatalogException if the specified file path can not be converted 291 * to a system id 292 */ 293 private URI getSystemId(String file) { 294 URL filepath; 295 if (file != null && file.length() > 0) { 296 try { 297 File f = new File(file); 298 if (baseURI != null && !f.isAbsolute()) { 299 filepath = new URL(baseURI, fixSlashes(file)); 300 return filepath.toURI(); 301 } else { 302 return resolveURI(file); 303 } 304 } catch (MalformedURLException | URISyntaxException e) { 305 CatalogMessages.reportRunTimeError(CatalogMessages.ERR_INVALID_PATH, 306 new Object[]{file}, e); 307 } 308 } 309 return null; 310 } 311 312 /** 313 * Resolves the specified uri. If the uri is relative, makes it absolute by 314 * the user.dir directory. 315 * 316 * @param uri The specified URI. 317 * @return The resolved URI 318 */ 319 private URI resolveURI(String uri) throws MalformedURLException { 320 if (uri == null) { 321 uri = ""; 322 } 323 324 URI temp = toURI(uri); 325 String str = temp.toASCIIString(); 326 String base = str.substring(0, str.lastIndexOf('/') + 1); 327 baseURI = new URL(str); 328 329 return temp; 330 } 331 332 /** 333 * Converts an URI string or file path to URI. 334 * 335 * @param uri an URI string or file path 336 * @return an URI 337 */ 338 private URI toURI(String uri) { 339 URI temp = null; 340 try { 341 URL url = new URL(uri); 342 temp = url.toURI(); 343 } catch (MalformedURLException | URISyntaxException mue) { 344 File file = new File(uri); 345 temp = file.toURI(); 346 } 347 return temp; 348 } 349 350 /** 351 * Returns a SAXParser instance 352 * @return a SAXParser instance 353 * @throws CatalogException if constructing a SAXParser failed 354 */ 355 private SAXParser getParser() { 356 SAXParser p = null; 357 try { 358 SAXParserFactory spf = new SAXParserFactoryImpl(); 359 spf.setNamespaceAware(true); 360 spf.setValidating(false); 361 spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); 362 p = spf.newSAXParser(); 363 } catch (ParserConfigurationException | SAXException e) { 364 CatalogMessages.reportRunTimeError(CatalogMessages.ERR_PARSING_FAILED, e); 365 } 366 return p; 367 } 368 369 /** 370 * Indicate that the catalog is empty 371 * 372 * @return True if the catalog is empty; False otherwise. 373 */ 374 public boolean isEmpty() { 375 return isEmpty; 376 } 377 378 @Override 379 public Stream<Catalog> catalogs() { 380 Iterator<Catalog> iter = new Iterator<Catalog>() { 381 Catalog nextCatalog = null; 382 383 //Current index of the input files 384 int inputFilesIndex = 0; 385 386 //Next catalog 387 int nextCatalogIndex = 0; 388 389 @Override 390 public boolean hasNext() { 391 if (nextCatalog != null) { 392 return true; 393 } else { 394 nextCatalog = nextCatalog(); 395 return (nextCatalog != null); 396 } 397 } 398 399 @Override 400 public Catalog next() { 401 if (nextCatalog != null || hasNext()) { 402 Catalog catalog = nextCatalog; 403 nextCatalog = null; 404 return catalog; 405 } else { 406 throw new NoSuchElementException(); 407 } 408 } 409 410 /** 411 * Returns the next alternative catalog. 412 * 413 * @return the next catalog if any 414 */ 415 private Catalog nextCatalog() { 416 Catalog c = null; 417 418 //Check those specified in nextCatalogs 419 if (nextCatalogs != null) { 420 while (c == null && nextCatalogIndex < nextCatalogs.size()) { 421 c = getCatalog(nextCatalogs.get(nextCatalogIndex++).getCatalogURI()); 422 } 423 } 424 425 //Check the input list 426 if (c == null && inputFiles != null) { 427 while (c == null && inputFilesIndex < inputFiles.size()) { 428 c = getCatalog(getSystemId(inputFiles.get(inputFilesIndex++))); 429 } 430 } 431 432 return c; 433 } 434 }; 435 436 return StreamSupport.stream(Spliterators.spliteratorUnknownSize( 437 iter, Spliterator.ORDERED | Spliterator.NONNULL), false); 438 } 439 440 /** 441 * Adds the catalog to the nextCatalog list 442 * 443 * @param catalog a catalog specified in a nextCatalog entry 444 */ 445 void addNextCatalog(NextCatalog catalog) { 446 if (catalog == null) { 447 return; 448 } 449 450 if (nextCatalogs == null) { 451 nextCatalogs = new ArrayList<>(); 452 } 453 454 nextCatalogs.add(catalog); 455 } 456 457 /** 458 * Loads all alternative catalogs. 459 */ 460 void loadNextCatalogs() { 461 //loads catalogs specified in nextCatalogs 462 if (nextCatalogs != null) { 463 for (NextCatalog next : nextCatalogs) { 464 getCatalog(next.getCatalogURI()); 465 } 466 } 467 468 //loads catalogs from the input list 469 if (inputFiles != null) { 470 for (String file : inputFiles) { 471 getCatalog(getSystemId(file)); 472 } 473 } 474 } 475 476 /** 477 * Returns a Catalog object by the specified path. 478 * 479 * @param path the path to a catalog 480 * @return a Catalog object 481 */ 482 Catalog getCatalog(URI uri) { 483 if (uri == null) { 484 return null; 485 } 486 487 Catalog c = null; 488 String path = uri.toASCIIString(); 489 490 if (verifyCatalogFile(uri)) { 491 c = getLoadedCatalog(path); 492 if (c == null) { 493 c = new CatalogImpl(this, features, path); 494 saveLoadedCatalog(path, c); 495 } 496 } 497 return c; 498 } 499 500 /** 501 * Saves a loaded Catalog. 502 * 503 * @param catalogId the catalogId associated with the Catalog object 504 * @param c the Catalog to be saved 505 */ 506 void saveLoadedCatalog(String catalogId, Catalog c) { 507 loadedCatalogs.put(catalogId, c); 508 } 509 510 /** 511 * Returns a count of all loaded catalogs, including delegate catalogs. 512 * 513 * @return a count of all loaded catalogs 514 */ 515 int loadedCatalogCount() { 516 return loadedCatalogs.size() + delegateCatalogs.size(); 517 } 518} 519