--- /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<BaseEntry> entries = new ArrayList<>();
+
+ //loaded delegated catalog by system id
+ Map<String, CatalogImpl> delegateCatalogs = new HashMap<>();
+
+ //A list of all loaded Catalogs, including this, and next catalogs
+ Map<String, CatalogImpl> 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<String> 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.
+ *
+ * <p>
+ * 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.
+ *
+ * <p>
+ * 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.
+ *
+ * <p>
+ * 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.
+ * <p>
+ * 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);
+ }
+}