--- /dev/null 2015-04-26 17:31:22.381138994 -0700 +++ new/src/java.xml/share/classes/javax/xml/catalog/GroupEntry.java 2015-10-26 15:21:53.095038534 -0700 @@ -0,0 +1,503 @@ +/* + * Copyright (c) 2015, 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.nio.file.Files; +import java.nio.file.Paths; +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 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; + + /** + * 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 + */ + public GroupEntry(CatalogEntryType type) { + super(type); + } + + /** + * 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; + } + /** + * Constructs a group entry. + * @param catalog The parent catalog + * @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; + } + + /** + * 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 An URI string if a mapping is found, or null otherwise. + */ + public String matchSystem(String systemId) { + 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) { + 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.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 An URI string if a mapping is found, or null otherwise. + */ + public String matchPublic(String publicId) { + //as the specification required + if (!isPreferPublic) { + return null; + } + + //match public entries + String match = null; + for (BaseEntry entry : entries) { + switch (entry.type) { + case PUBLIC: + match = ((PublicEntry) 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 An 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 isSystem The flag to indicate whether the delegate is system or + * public + * @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 = loadCatalog(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. + */ + void loadDelegateCatalogs() { + entries.stream() + .filter((entry) -> (entry.type == CatalogEntryType.DELEGATESYSTEM || + entry.type == CatalogEntryType.DELEGATEPUBLIC || + entry.type == CatalogEntryType.DELEGATEURI)) + .map((entry) -> (AltCatalog)entry) + .forEach((altCatalog) -> { + loadCatalog(altCatalog.getCatalogURI()); + }); + } + + /** + * Loads a delegate catalog by the catalogId specified. + * @param catalogId the catalog Id + */ + Catalog loadCatalog(URI catalogURI) { + Catalog delegateCatalog = null; + if (catalogURI != null) { + String catalogId = catalogURI.toASCIIString(); + delegateCatalog = getLoadedCatalog(catalogId); + if (delegateCatalog == null) { + if (verifyCatalogFile(catalogURI)) { + delegateCatalog = new CatalogImpl(catalog, features, catalogId); + 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 + */ + Catalog getLoadedCatalog(String catalogId) { + Catalog c = null; + + //checl 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 catalogId The URI to a catalog + * @throws CatalogException if circular reference is found. + * @return true if the catalogId passed verification, false otherwise + */ + final boolean verifyCatalogFile(URI catalogURI) { + if (catalogURI == null) { + return false; + } + + //Ignore it if it doesn't exist + if (!Files.exists(Paths.get(catalogURI))) { + return false; + } + + String catalogId = catalogURI.toASCIIString(); + if (catalogsSearched.contains(catalogId)) { + CatalogMessages.reportRunTimeError(CatalogMessages.ERR_CIRCULAR_REFERENCE, + new Object[]{CatalogMessages.sanitize(catalogId)}); + } + + return true; + } + +}