diff -r 4ebc2e2fb97c -r 71c04702a3d5 src/java.xml/share/classes/javax/xml/catalog/GroupEntry.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.xml/share/classes/javax/xml/catalog/GroupEntry.java Tue Sep 12 19:03:39 2017 +0200 @@ -0,0 +1,553 @@ +/* + * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package javax.xml.catalog; + +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Represents a group entry. + * + * @since 9 + */ +class GroupEntry extends BaseEntry { + static final int ATTRIBUTE_PREFER = 0; + static final int ATTRIBUTE_DEFFER = 1; + static final int ATTRIBUTE_RESOLUTION = 2; + + //Unmodifiable features when the Catalog is created + CatalogFeatures features; + + //Value of the prefer attribute + boolean isPreferPublic = true; + + //The parent of the catalog instance + CatalogImpl parent = null; + + //The catalog instance this group belongs to + CatalogImpl catalog; + + //A list of all entries in a catalog or group + List entries = new ArrayList<>(); + + //loaded delegated catalog by system id + Map delegateCatalogs = new HashMap<>(); + + //A list of all loaded Catalogs, including this, and next catalogs + Map loadedCatalogs = new HashMap<>(); + + /* + A list of Catalog Ids that have already been searched in a matching + operation. Check this list before constructing new Catalog to avoid circular + reference. + */ + List catalogsSearched = new ArrayList<>(); + + //A flag to indicate whether the current match is a system or uri + boolean isInstantMatch = false; + + //A match of a rewrite type + String rewriteMatch = null; + + //The length of the longest match of a rewrite type + int longestRewriteMatch = 0; + + //A match of a suffix type + String suffixMatch = null; + + //The length of the longest match of a suffix type + int longestSuffixMatch = 0; + + //Indicate whether a system entry has been searched + boolean systemEntrySearched = false; + + /** + * PreferType represents possible values of the prefer property + */ + public static enum PreferType { + PUBLIC("public"), + SYSTEM("system"); + + final String literal; + + PreferType(String literal) { + this.literal = literal; + } + + public boolean prefer(String prefer) { + return literal.equals(prefer); + } + } + + /** + * PreferType represents possible values of the resolve property + */ + public static enum ResolveType { + STRICT(CatalogFeatures.RESOLVE_STRICT), + CONTINUE(CatalogFeatures.RESOLVE_CONTINUE), + IGNORE(CatalogFeatures.RESOLVE_IGNORE); + + final String literal; + + ResolveType(String literal) { + this.literal = literal; + } + + static public ResolveType getType(String resolveType) { + for (ResolveType type : ResolveType.values()) { + if (type.isType(resolveType)) { + return type; + } + } + return null; + } + + public boolean isType(String type) { + return literal.equals(type); + } + } + + /** + * Constructs a GroupEntry + * + * @param type the type of the entry + * @param parent the parent Catalog + */ + public GroupEntry(CatalogEntryType type, CatalogImpl parent) { + super(type); + this.parent = parent; + } + + /** + * Constructs a group entry. + * + * @param base The baseURI attribute + * @param attributes The attributes + */ + public GroupEntry(String base, String... attributes) { + this(null, base, attributes); + } + + /** + * Resets the group entry to its initial state. + */ + public void reset() { + isInstantMatch = false; + rewriteMatch = null; + longestRewriteMatch = 0; + suffixMatch = null; + longestSuffixMatch = 0; + systemEntrySearched = false; + } + /** + * Constructs a group entry. + * @param catalog the catalog this GroupEntry belongs to + * @param base the baseURI attribute + * @param attributes the attributes + */ + public GroupEntry(CatalogImpl catalog, String base, String... attributes) { + super(CatalogEntryType.GROUP, base); + setPrefer(attributes[ATTRIBUTE_PREFER]); + this.catalog = catalog; + } + + /** + * Sets the catalog for this GroupEntry. + * + * @param catalog the catalog this GroupEntry belongs to + */ + void setCatalog(CatalogImpl catalog) { + this.catalog = catalog; + } + + /** + * Adds an entry. + * + * @param entry The entry to be added. + */ + public void addEntry(BaseEntry entry) { + entries.add(entry); + } + + /** + * Sets the prefer property. If the value is null or empty, or any String + * other than the defined, it will be assumed as the default value. + * + * @param value The value of the prefer attribute + */ + public final void setPrefer(String value) { + isPreferPublic = PreferType.PUBLIC.prefer(value); + } + + /** + * Queries the prefer attribute + * + * @return true if the prefer attribute is set to system, false if not. + */ + public boolean isPreferPublic() { + return isPreferPublic; + } + + /** + * Attempt to find a matching entry in the catalog by systemId. + * + *

+ * The method searches through the system-type entries, including system, + * rewriteSystem, systemSuffix, delegateSystem, and group entries in the + * current catalog in order to find a match. + * + * + * @param systemId The system identifier of the external entity being + * referenced. + * + * @return a URI string if a mapping is found, or null otherwise. + */ + public String matchSystem(String systemId) { + systemEntrySearched = true; + String match = null; + for (BaseEntry entry : entries) { + switch (entry.type) { + case SYSTEM: + match = ((SystemEntry) entry).match(systemId); + //if there's a matching system entry, use it + if (match != null) { + isInstantMatch = true; + return match; + } + break; + case REWRITESYSTEM: + match = ((RewriteSystem) entry).match(systemId, longestRewriteMatch); + if (match != null) { + rewriteMatch = match; + longestRewriteMatch = ((RewriteSystem) entry).getSystemIdStartString().length(); + } + break; + case SYSTEMSUFFIX: + match = ((SystemSuffix) entry).match(systemId, longestSuffixMatch); + if (match != null) { + suffixMatch = match; + longestSuffixMatch = ((SystemSuffix) entry).getSystemIdSuffix().length(); + } + break; + case GROUP: + GroupEntry grpEntry = (GroupEntry) entry; + match = grpEntry.matchSystem(systemId); + if (grpEntry.isInstantMatch) { + //use it if there is a match of the system type + return match; + } else if (grpEntry.longestRewriteMatch > longestRewriteMatch) { + longestRewriteMatch = grpEntry.longestRewriteMatch; + rewriteMatch = match; + } else if (grpEntry.longestSuffixMatch > longestSuffixMatch) { + longestSuffixMatch = grpEntry.longestSuffixMatch; + suffixMatch = match; + } + break; + } + } + + if (longestRewriteMatch > 0) { + return rewriteMatch; + } else if (longestSuffixMatch > 0) { + return suffixMatch; + } + + //if no single match is found, try delegates + return matchDelegate(CatalogEntryType.DELEGATESYSTEM, systemId); + } + + /** + * Attempt to find a matching entry in the catalog by publicId. + * + *

+ * The method searches through the public-type entries, including public, + * delegatePublic, and group entries in the current catalog in order to find + * a match. + * + * + * @param publicId The public identifier of the external entity being + * referenced. + * + * @return a URI string if a mapping is found, or null otherwise. + */ + public String matchPublic(String publicId) { + /* + When both public and system identifiers are specified, and prefer is + not public (that is, system), only system entry will be used. + */ + if (!isPreferPublic && systemEntrySearched) { + return null; + } + //match public entries + String match = null; + for (BaseEntry entry : entries) { + switch (entry.type) { + case PUBLIC: + match = ((PublicEntry) entry).match(publicId); + break; + case URI: + match = ((UriEntry) entry).match(publicId); + break; + case GROUP: + match = ((GroupEntry) entry).matchPublic(publicId); + break; + } + if (match != null) { + return match; + } + } + + //if no single match is found, try delegates + return matchDelegate(CatalogEntryType.DELEGATEPUBLIC, publicId); + } + + /** + * Attempt to find a matching entry in the catalog by the uri element. + * + *

+ * The method searches through the uri-type entries, including uri, + * rewriteURI, uriSuffix, delegateURI and group entries in the current + * catalog in order to find a match. + * + * + * @param uri The URI reference of a resource. + * + * @return a URI string if a mapping is found, or null otherwise. + */ + public String matchURI(String uri) { + String match = null; + for (BaseEntry entry : entries) { + switch (entry.type) { + case URI: + match = ((UriEntry) entry).match(uri); + if (match != null) { + isInstantMatch = true; + return match; + } + break; + case REWRITEURI: + match = ((RewriteUri) entry).match(uri, longestRewriteMatch); + if (match != null) { + rewriteMatch = match; + longestRewriteMatch = ((RewriteUri) entry).getURIStartString().length(); + } + break; + case URISUFFIX: + match = ((UriSuffix) entry).match(uri, longestSuffixMatch); + if (match != null) { + suffixMatch = match; + longestSuffixMatch = ((UriSuffix) entry).getURISuffix().length(); + } + break; + case GROUP: + GroupEntry grpEntry = (GroupEntry) entry; + match = grpEntry.matchURI(uri); + if (grpEntry.isInstantMatch) { + //use it if there is a match of the uri type + return match; + } else if (grpEntry.longestRewriteMatch > longestRewriteMatch) { + rewriteMatch = match; + } else if (grpEntry.longestSuffixMatch > longestSuffixMatch) { + suffixMatch = match; + } + break; + } + } + + if (longestRewriteMatch > 0) { + return rewriteMatch; + } else if (longestSuffixMatch > 0) { + return suffixMatch; + } + + //if no single match is found, try delegates + return matchDelegate(CatalogEntryType.DELEGATEURI, uri); + } + + /** + * Matches delegatePublic or delegateSystem against the specified id + * + * @param type the type of the Catalog entry + * @param id the system or public id to be matched + * @return the URI string if a mapping is found, or null otherwise. + */ + private String matchDelegate(CatalogEntryType type, String id) { + String match = null; + int longestMatch = 0; + URI catalogId = null; + URI temp; + + //Check delegate types in the current catalog + for (BaseEntry entry : entries) { + if (entry.type == type) { + if (type == CatalogEntryType.DELEGATESYSTEM) { + temp = ((DelegateSystem)entry).matchURI(id, longestMatch); + } else if (type == CatalogEntryType.DELEGATEPUBLIC) { + temp = ((DelegatePublic)entry).matchURI(id, longestMatch); + } else { + temp = ((DelegateUri)entry).matchURI(id, longestMatch); + } + if (temp != null) { + longestMatch = entry.getMatchId().length(); + catalogId = temp; + } + } + } + + //Check delegate Catalogs + if (catalogId != null) { + Catalog delegateCatalog = loadDelegateCatalog(catalog, catalogId); + + if (delegateCatalog != null) { + if (type == CatalogEntryType.DELEGATESYSTEM) { + match = delegateCatalog.matchSystem(id); + } else if (type == CatalogEntryType.DELEGATEPUBLIC) { + match = delegateCatalog.matchPublic(id); + } else { + match = delegateCatalog.matchURI(id); + } + } + } + + return match; + } + + /** + * Loads all delegate catalogs. + * + * @param parent the parent catalog of the delegate catalogs + */ + void loadDelegateCatalogs(CatalogImpl parent) { + entries.stream() + .filter((entry) -> (entry.type == CatalogEntryType.DELEGATESYSTEM || + entry.type == CatalogEntryType.DELEGATEPUBLIC || + entry.type == CatalogEntryType.DELEGATEURI)) + .map((entry) -> (AltCatalog)entry) + .forEach((altCatalog) -> { + loadDelegateCatalog(parent, altCatalog.getCatalogURI()); + }); + } + + /** + * Loads a delegate catalog by the catalogId specified. + * + * @param parent the parent catalog of the delegate catalog + * @param catalogURI the URI to the catalog + */ + Catalog loadDelegateCatalog(CatalogImpl parent, URI catalogURI) { + CatalogImpl delegateCatalog = null; + if (catalogURI != null) { + String catalogId = catalogURI.toASCIIString(); + if (verifyCatalogFile(parent, catalogURI)) { + delegateCatalog = getLoadedCatalog(catalogId); + if (delegateCatalog == null) { + delegateCatalog = new CatalogImpl(parent, features, catalogURI); + delegateCatalog.load(); + delegateCatalogs.put(catalogId, delegateCatalog); + } + } + } + + return delegateCatalog; + } + + /** + * Returns a previously loaded Catalog object if found. + * + * @param catalogId The systemId of a catalog + * @return a Catalog object previously loaded, or null if none in the saved + * list + */ + CatalogImpl getLoadedCatalog(String catalogId) { + CatalogImpl c = null; + + //check delegate Catalogs + c = delegateCatalogs.get(catalogId); + if (c == null) { + //check other loaded Catalogs + c = loadedCatalogs.get(catalogId); + } + + return c; + } + + + /** + * Verifies that the catalog file represented by the catalogId exists. If it + * doesn't, returns false to ignore it as specified in the Catalog + * specification, section 8. Resource Failures. + *

+ * Verifies that the catalog represented by the catalogId has not been + * searched or is not circularly referenced. + * + * @param parent the parent of the catalog to be loaded + * @param catalogURI the URI to the catalog + * @throws CatalogException if circular reference is found. + * @return true if the catalogId passed verification, false otherwise + */ + final boolean verifyCatalogFile(CatalogImpl parent, URI catalogURI) { + if (catalogURI == null) { + return false; + } + + //Ignore it if it doesn't exist + if (Util.isFileUri(catalogURI) && + !Util.isFileUriExist(catalogURI, false)) { + return false; + } + + String catalogId = catalogURI.toASCIIString(); + if (catalogsSearched.contains(catalogId) || isCircular(parent, catalogId)) { + CatalogMessages.reportRunTimeError(CatalogMessages.ERR_CIRCULAR_REFERENCE, + new Object[]{CatalogMessages.sanitize(catalogId)}); + } + + return true; + } + + /** + * Checks whether the catalog is circularly referenced + * + * @param parent the parent of the catalog to be loaded + * @param systemId the system identifier of the catalog to be loaded + * @return true if is circular, false otherwise + */ + boolean isCircular(CatalogImpl parent, String systemId) { + // first, check the parent of the catalog to be loaded + if (parent == null) { + return false; + } + + if (parent.systemId.equals(systemId)) { + return true; + } + + // next, check parent's parent + return parent.isCircular(parent.parent, systemId); + } +}