/* * Copyright (c) 2007, Dennis M. Sosnoski All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of * JiBX nor the names of its contributors may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.jibx.schema.codegen.custom; import java.util.HashMap; import java.util.Map; import org.apache.log4j.Logger; import org.jibx.binding.util.StringArray; import org.jibx.runtime.IUnmarshallingContext; import org.jibx.schema.elements.SchemaElement; import org.jibx.schema.support.LazyList; import org.jibx.schema.validation.ValidationContext; import org.jibx.schema.validation.ValidationProblem; /** * Customization information for a set of schemas. * * @author Dennis M. Sosnoski */ public class SchemasetCustom extends SchemaRootBase { /** Logger for class. */ private static final Logger s_logger = Logger.getLogger(SchemasetCustom.class.getName()); /** Enumeration of allowed attribute names */ public static final StringArray s_allowedAttributes = new StringArray(new String[] { "names", "namespaces" }, SchemaRootBase.s_allowedAttributes); // // Unmarshalled data /** Schema name patterns. */ private String[] m_names; /** Schema namespace patterns. */ private String[] m_namespaces; // // Instance data /** Map from schema file name to customization. */ private final Map m_schemaMap; /** * Constructor. * * @param parent */ public SchemasetCustom(SchemasetCustom parent) { super(parent); m_schemaMap = new HashMap(); } /** * Get schema name match patterns. * * @return names (null if not set) */ public String[] getNames() { return m_names; } /** * Make sure all attributes are defined. * * @param uctx unmarshalling context */ private void preSet(IUnmarshallingContext uctx) { validateAttributes(uctx, s_allowedAttributes); } /** * Check if a schema is included in this set. * * @param name schema file name * @param schema actual schema * @return true if in set, false if not */ public boolean isInSet(String name, SchemaElement schema) { // check for match on file name boolean match = true; if (m_names != null) { match = false; for (int i = 0; i < m_names.length; i++) { if (isPatternMatch(name, m_names[i])) { match = true; break; } } } // check for match on target namespace if (m_namespaces != null) { match = false; String ns = schema.getTargetNamespace(); if (ns == null) { ns = ""; } for (int i = 0; i < m_namespaces.length; i++) { if (isPatternMatch(ns, m_namespaces[i])) { match = true; break; } } } // return 'and' of two tests return match; } /** * Get existing schema customization information. * * @param name schema file name * @return customization */ public SchemaCustom getCustomization(String name) { return (SchemaCustom)m_schemaMap.get(name); } /** * Get schema customization information, creating it if it doesn't already exist. * * @param name schema file name * @param schema actual schema * @param vctx validation context for reporting errors * @return customization */ public SchemaCustom forceCustomization(String name, SchemaElement schema, ValidationContext vctx) { SchemaCustom custom = (SchemaCustom)m_schemaMap.get(name); if (custom == null) { // check for unique match to schema customization for (int i = 0; i < getChildren().size(); i++) { SchemaRootBase child = (SchemaRootBase)getChildren().get(i); if (child instanceof SchemaCustom && ((SchemaCustom)child).checkMatch(name, schema)) { if (custom == null) { custom = (SchemaCustom)child; } else { vctx.addError("Multiple matches to schema " + name + " (first match " + ValidationProblem.componentDescription(custom) + ')', child); } } } // create default customization if no match found if (custom == null) { custom = new SchemaCustom(this); } // add customization to map and link to schema m_schemaMap.put(name, custom); custom.setSchema(name, schema); } return custom; } /** * Factory used during unmarshalling. * * @param ictx * @return instance */ private static SchemasetCustom factory(IUnmarshallingContext ictx) { return new SchemasetCustom((SchemasetCustom)getContainingObject(ictx)); } /** * Checks if a name matches a pattern. This method accepts one or more '*' wildcard * characters in the pattern, calling itself recursively in order to handle multiple wildcards. * * @param name * @param pattern match pattern * @return true if pattern matched, false if not */ public static boolean isPatternMatch(String name, String pattern) { // check special match cases first if (pattern.length() == 0) { return name.length() == 0; } else if (pattern.charAt(0) == '*') { // check if the wildcard is all that's left of pattern if (pattern.length() == 1) { return true; } else { // check if another wildcard follows next segment of text pattern = pattern.substring(1); int split = pattern.indexOf('*'); if (split > 0) { // recurse on each match to text segment String piece = pattern.substring(0, split); pattern = pattern.substring(split); int offset = -1; while ((offset = name.indexOf(piece, ++offset)) > 0) { int end = offset + piece.length(); if (isPatternMatch(name.substring(end), pattern)) { return true; } } } else { // no more wildcards, need exact match to end of name return name.endsWith(pattern); } } } else { // check for leading text before first wildcard int split = pattern.indexOf('*'); if (split > 0) { // match leading text to start of name String piece = pattern.substring(0, split); if (name.startsWith(piece)) { return isPatternMatch(name.substring(split), pattern.substring(split)); } else { return false; } } else { // no wildcards, need exact match return name.equals(pattern); } } return false; } /** * Recursively check that each schema customization has been matched to a schema. A warning is generated for any * customization without a matching schema. * * @param vctx */ public void checkSchemas(ValidationContext vctx) { // check for unique match to schema customization for (int i = 0; i < getChildren().size(); i++) { SchemaRootBase child = (SchemaRootBase)getChildren().get(i); if (child instanceof SchemaCustom) { SchemaCustom custom = (SchemaCustom)child; if (custom.getSchema() == null) { vctx.addError("No schema loaded for customization ", custom); } } else if (child instanceof SchemasetCustom) { ((SchemasetCustom)child).checkSchemas(vctx); } } } /** * Validate and finalize customization information. This override of the base class implementation also invokes the * same method on any nested schemasets in order to make sure that the type substitution map and active facets mask * will be available for use by nested schemas. * * @param vctx validation context * @return true if valid, false if not */ public boolean validate(ValidationContext vctx) { boolean valid = super.validate(vctx); if (valid) { LazyList children = getChildren(); for (int i = 0; i < children.size(); i++) { Object child = children.get(i); if (child instanceof SchemasetCustom) { if (!((SchemasetCustom)child).validate(vctx)) { valid = false; } } } } return valid; } }