/* * @(#)$Id: TypeIncubator.java,v 1.21 2002/10/06 18:06:43 kk122374 Exp $ * * Copyright 2001 Sun Microsystems, Inc. All Rights Reserved. * * This software is the proprietary information of Sun Microsystems, Inc. * Use is subject to license terms. * */ package com.sun.msv.datatype.xsd; import java.util.Map; import java.util.Vector; import java.util.Iterator; import java.math.BigInteger; import org.relaxng.datatype.ValidationContext; import org.relaxng.datatype.Datatype; import org.relaxng.datatype.DatatypeBuilder; import org.relaxng.datatype.DatatypeException; /** * derives a new type by adding facets. * * @author <a href="mailto:kohsuke.kawaguchi@eng.sun.com">Kohsuke KAWAGUCHI</a> */ public class TypeIncubator { /** storage for non-repeatable facets */ private final Map impl = new java.util.HashMap(); /** base type */ private final XSDatatypeImpl baseType; public TypeIncubator( XSDatatype baseType ) { this.baseType = (XSDatatypeImpl)baseType; if( baseType==null ) throw new IllegalArgumentException(); } /** * adds a facet to the type. * * @deprecated * please use the addFacet method, which is better named. */ public void add( String name, String strValue, boolean fixed, ValidationContext context ) throws DatatypeException { addFacet( name, strValue, context ); } /** adds a facet to the type. * * @exception DatatypeException * when given facet is already specified */ public void addFacet( String name, String strValue, ValidationContext context ) throws DatatypeException { if(baseType instanceof ErrorType) return; // silently ignore any further error // checks applicability of the facet switch( baseType.isFacetApplicable(name) ) { case XSDatatypeImpl.APPLICABLE: break; case XSDatatypeImpl.NOT_ALLOWED: throw new DatatypeException( XSDatatypeImpl.localize( XSDatatypeImpl.ERR_NOT_APPLICABLE_FACET, name ) ); default: throw new Error(); // assertion failed } Object value; if( isValueFacet(name) ) { value = baseType.createValue(strValue,context); if(value==null) throw new DatatypeException( XSDatatypeImpl.localize( XSDatatypeImpl.ERR_INVALID_VALUE_FOR_THIS_TYPE, strValue, baseType.displayName() ) ); } else value = strValue; if( isRepeatable(name) ) { Vector v; if( impl.containsKey(name) ) v = (Vector)impl.get(name); else impl.put(name, v=new Vector()); v.add(value); } else { if( impl.containsKey(name) ) throw new DatatypeException( XSDatatypeImpl.localize( XSDatatypeImpl.ERR_DUPLICATE_FACET, name ) ); impl.put(name,value); } } final static private String[][] exclusiveFacetPairs = new String[][]{ new String[]{ XSDatatypeImpl.FACET_LENGTH, XSDatatypeImpl.FACET_MINLENGTH }, new String[]{ XSDatatypeImpl.FACET_LENGTH, XSDatatypeImpl.FACET_MAXLENGTH }, new String[]{ XSDatatypeImpl.FACET_MAXINCLUSIVE, XSDatatypeImpl.FACET_MAXEXCLUSIVE }, new String[]{ XSDatatypeImpl.FACET_MININCLUSIVE, XSDatatypeImpl.FACET_MINEXCLUSIVE } }; /** @deprecated */ public XSDatatypeImpl derive( String newName ) throws DatatypeException { return derive("",newName); } /** * derives a new datatype from a datatype by facets that were set. * * It is completely legal to use null as the newTypeName parameter, * which means the derivation of an anonymous datatype. * * @exception DatatypeException * DatatypeException is thrown if derivation is somehow invalid. * For example, not applicable facets are applied, or enumeration * has invalid values, ... things like that. */ public XSDatatypeImpl derive( String newNameUri, String newLocalName ) throws DatatypeException { if(baseType instanceof ErrorType) return baseType; if( baseType.isFinal(XSDatatype.DERIVATION_BY_RESTRICTION) ) throw new DatatypeException( XSDatatypeImpl.localize( XSDatatypeImpl.ERR_INVALID_BASE_TYPE, baseType.displayName() ) ); if( isEmpty() ) { // if no facet is specified, and user wants anonymous type, // then no need to create another object. // TODO: for the type-derivation-OK test to work correctly, // maybe we need to wrap this by a FinalComponent. if( newNameUri==null&& newLocalName==null ) return baseType; // using FinalComponent as a wrapper, // so that the new type object can have its own name. return new FinalComponent(newNameUri,newLocalName,baseType,0); } XSDatatypeImpl r = baseType; // start from current datatype // TODO : make sure that the following interpretation is true /* several facet consistency check is done here. those which are done in this time are: - length and (minLength/maxLength) are exclusive - maxInclusive and maxExclusive are exclusive - minInclusive and minExclusive are exclusive those are exclusive within the one restriction; that is, it is legal to derive types in the following way: <simpleType name="foo"> <restriction baseType="string"> <minLength value="3" /> </restrction> </simpleType> <simpleType name="bar"> <restriction baseType="foo"> <length value="5" /> </restrction> </simpleType> although the following is considered as an error <simpleType name="bar"> <restriction baseType="foo"> <length value="5" /> <minLength value="3" /> </restrction> </simpleType> This method is the perfect place to perform this kind of check. */ // makes sure that no mutually exclusive facets are specified for( int i=0; i<exclusiveFacetPairs.length; i++ ) if( contains( exclusiveFacetPairs[i][0]) && contains( exclusiveFacetPairs[i][1]) ) throw new DatatypeException( XSDatatypeImpl.localize( XSDatatypeImpl.ERR_X_AND_Y_ARE_EXCLUSIVE, exclusiveFacetPairs[i][0], exclusiveFacetPairs[i][1] ) ); if( contains(XSDatatypeImpl.FACET_TOTALDIGITS) ) r = new TotalDigitsFacet ( newNameUri,newLocalName, r, this ); if( contains(XSDatatypeImpl.FACET_FRACTIONDIGITS) ) r = new FractionDigitsFacet ( newNameUri,newLocalName, r, this ); if( contains(XSDatatypeImpl.FACET_MININCLUSIVE) ) r = new MinInclusiveFacet ( newNameUri,newLocalName, r, this ); if( contains(XSDatatypeImpl.FACET_MAXINCLUSIVE) ) r = new MaxInclusiveFacet ( newNameUri,newLocalName, r, this ); if( contains(XSDatatypeImpl.FACET_MINEXCLUSIVE) ) r = new MinExclusiveFacet ( newNameUri,newLocalName, r, this ); if( contains(XSDatatypeImpl.FACET_MAXEXCLUSIVE) ) r = new MaxExclusiveFacet ( newNameUri,newLocalName, r, this ); if( contains(XSDatatypeImpl.FACET_LENGTH) ) r = new LengthFacet ( newNameUri,newLocalName, r, this ); if( contains(XSDatatypeImpl.FACET_MINLENGTH) ) r = new MinLengthFacet ( newNameUri,newLocalName, r, this ); if( contains(XSDatatypeImpl.FACET_MAXLENGTH) ) r = new MaxLengthFacet ( newNameUri,newLocalName, r, this ); if( contains(XSDatatypeImpl.FACET_WHITESPACE) ) r = new WhiteSpaceFacet ( newNameUri,newLocalName, r, this ); if( contains(XSDatatypeImpl.FACET_PATTERN) ) r = new PatternFacet ( newNameUri,newLocalName, r, this ); if( contains(XSDatatypeImpl.FACET_ENUMERATION) ) r = new EnumerationFacet ( newNameUri,newLocalName, r, this ); // additional facet consistency check { DataTypeWithFacet o1,o2; // check that minLength <= maxLength o1 = r.getFacetObject(XSDatatypeImpl.FACET_MAXLENGTH); o2 = r.getFacetObject(XSDatatypeImpl.FACET_MINLENGTH); if( o1!=null && o2!=null && ((MaxLengthFacet)o1).maxLength < ((MinLengthFacet)o2).minLength ) throw reportFacetInconsistency( newLocalName, o1,XSDatatypeImpl.FACET_MAXLENGTH, o2,XSDatatypeImpl.FACET_MINLENGTH ); // check that scale <= precision o1 = r.getFacetObject(XSDatatypeImpl.FACET_FRACTIONDIGITS); o2 = r.getFacetObject(XSDatatypeImpl.FACET_TOTALDIGITS); if( o1!=null && o2!=null && ((FractionDigitsFacet)o1).scale > ((TotalDigitsFacet)o2).precision ) throw reportFacetInconsistency( newLocalName, o1,XSDatatypeImpl.FACET_FRACTIONDIGITS, o2,XSDatatypeImpl.FACET_TOTALDIGITS ); // check that minInclusive <= maxInclusive checkRangeConsistency( r, XSDatatypeImpl.FACET_MININCLUSIVE, XSDatatypeImpl.FACET_MAXINCLUSIVE ); checkRangeConsistency( r, XSDatatypeImpl.FACET_MINEXCLUSIVE, XSDatatypeImpl.FACET_MAXEXCLUSIVE ); // TODO : I'm not sure that the following two checks should be done or not. // since the spec doesn't have these constraints checkRangeConsistency( r, XSDatatypeImpl.FACET_MININCLUSIVE, XSDatatypeImpl.FACET_MAXEXCLUSIVE ); checkRangeConsistency( r, XSDatatypeImpl.FACET_MINEXCLUSIVE, XSDatatypeImpl.FACET_MAXINCLUSIVE ); } return r; } /** * check (min,max) facet specification and makes sure that * they are consistent * * @exception BadTypeException * when two facets are inconsistent */ private static void checkRangeConsistency( XSDatatypeImpl newType, String facetName1, String facetName2 ) throws DatatypeException { DataTypeWithFacet o1 = newType.getFacetObject(facetName1); DataTypeWithFacet o2 = newType.getFacetObject(facetName2); if( o1!=null && o2!=null ) { final int c = ((Comparator)o1.getConcreteType()).compare( ((RangeFacet)o1).limitValue, ((RangeFacet)o2).limitValue ); if( c==Comparator.GREATER ) throw reportFacetInconsistency( newType.displayName(), o1,facetName1, o2,facetName2 ); } } /** * creates a BadTypeException with appropriate error message. * * this method is only useful for reporting facet consistency violation. */ private static DatatypeException reportFacetInconsistency( String newName, DataTypeWithFacet o1, String facetName1, DataTypeWithFacet o2, String facetName2 ) { // analyze the situation further so as to // provide better error messages String o1typeName = o1.getName(); String o2typeName = o2.getName(); if(o1typeName.equals(o2typeName)) // o1typeName==o2typeName==newName return new DatatypeException( XSDatatypeImpl.localize( XSDatatypeImpl.ERR_INCONSISTENT_FACETS_1, facetName1, facetName2 ) ); if(o1typeName.equals(newName)) // o2 must be specified in somewhere in the derivation chain return new DatatypeException( XSDatatypeImpl.localize( XSDatatypeImpl.ERR_INCONSISTENT_FACETS_2, facetName1, o2.displayName(), facetName2 ) ); if(o2typeName.equals(newName)) // vice versa return new DatatypeException( XSDatatypeImpl.localize( XSDatatypeImpl.ERR_INCONSISTENT_FACETS_2, facetName2, o1.displayName(), facetName1 ) ); // this is not possible // because facet consistency check is done by every derivation. throw new IllegalStateException(); } /** * returns true if the specified facet is a facet that needs value-space-level check. */ private static boolean isValueFacet( String facetName ) { return facetName.equals(XSDatatypeImpl.FACET_ENUMERATION) || facetName.equals(XSDatatypeImpl.FACET_MAXEXCLUSIVE) || facetName.equals(XSDatatypeImpl.FACET_MINEXCLUSIVE) || facetName.equals(XSDatatypeImpl.FACET_MAXINCLUSIVE) || facetName.equals(XSDatatypeImpl.FACET_MININCLUSIVE); } /** * returns true if the specified facet is a facet which can be set multiple times. */ private static boolean isRepeatable( String facetName ) { return facetName.equals(XSDatatypeImpl.FACET_ENUMERATION) || facetName.equals(XSDatatypeImpl.FACET_PATTERN); } /** * gets a value of non-repeatable facet * * the behavior is undefined when the specified facetName doesn't exist * in this map. */ public Object getFacet( String facetName ) { return impl.get(facetName); } /** * gets a value of repeatable facet * * the behavior is undefined when the specified facetName doesn't exist * in this map. */ public Vector getVector(String facetName) { return (Vector)impl.get(facetName); } /** * gets a value of non-repeatable facet as a positive integer * * the behavior is undefined when the specified facetName doesn't exist * in this map. * * @exception BadTypeException * if the parameter cannot be parsed as a positive integer */ public int getPositiveInteger( String facetName ) throws DatatypeException { try { // TODO : is this implementation correct? int value = Integer.parseInt((String)getFacet(facetName)); if( value>0 ) return value; } catch( NumberFormatException e ) { // let's try BigInteger to see if the value is actually positive try { // if we can parse it in BigInteger, then treat is as Integer.MAX_VALUE // this will work for most cases, I suppose. if(new BigInteger((String)getFacet(facetName)).signum()>0) return Integer.MAX_VALUE; } catch(NumberFormatException ee) {;} } throw new DatatypeException( XSDatatypeImpl.localize( XSDatatypeImpl.ERR_FACET_MUST_BE_POSITIVE_INTEGER, facetName ) ); } /** * gets a value of non-repeatable facet as a non-negative integer * * the behavior is undefined when the specified facetName doesn't exist * in this map. * * @exception BadTypeException * if the parameter cannot be parsed as a non-negative integer */ public int getNonNegativeInteger( String facetName ) throws DatatypeException { try { // TODO : is this implementation correct? Can I use Integer.parseInt? int value = Integer.parseInt((String)getFacet(facetName)); if( value>=0 ) return value; } catch( NumberFormatException e ) { ; } throw new DatatypeException( XSDatatypeImpl.localize( XSDatatypeImpl.ERR_FACET_MUST_BE_NON_NEGATIVE_INTEGER, facetName ) ); } /** checks if the specified facet was added to this map */ private boolean contains( String facetName ) { return impl.containsKey(facetName); } /** returns true if no facet is added */ public boolean isEmpty() { return impl.isEmpty(); } /** * dumps the contents to the given object. * this method is for debug use only. */ public void dump( java.io.PrintStream out ) { Iterator itr = impl.keySet().iterator(); while(itr.hasNext()) { String facetName = (String)itr.next(); Object value = impl.get(facetName); if(value instanceof Vector) { out.println( facetName + " :"); Vector v = (Vector)value; for( int i=0; i<v.size(); i++ ) out.println( " " +v.elementAt(i) ); } else out.println( facetName + " : " + value ); } } /** * gets names of the facets in this object * this method is used to produce error messages. */ public String getFacetNames() { String r=""; Iterator itr = impl.keySet().iterator(); while(itr.hasNext()) { if(r.length()!=0) r+=", "; r += (String)itr.next(); } return r; } }