src/java.xml/share/classes/javax/xml/catalog/GroupEntry.java
changeset 47216 71c04702a3d5
parent 44380 0197177795e9
child 53146 62a4355dc9c8
--- /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);
+    }
+}