/* * Copyright 2013 Guidewire Software, Inc. */ package gw.internal.xml.xsd.typeprovider; import gw.config.CommonServices; import gw.internal.xml.XmlElementInternals; import gw.internal.xml.XmlTypeInstanceInternals; import gw.internal.xml.xsd.typeprovider.schema.XmlSchemaElement; import gw.internal.xml.xsd.typeprovider.schema.XmlSchemaObject; import gw.internal.xml.xsd.typeprovider.schema.XmlSchemaType; import gw.lang.reflect.ConstructorInfoBuilder; import gw.lang.reflect.IConstructorHandler; import gw.lang.reflect.IConstructorInfo; import gw.lang.reflect.ILocationAwareFeature; import gw.lang.reflect.IMethodCallHandler; import gw.lang.reflect.IMethodInfo; import gw.lang.reflect.IPropertyAccessor; import gw.lang.reflect.IPropertyInfo; import gw.lang.reflect.IType; import gw.lang.reflect.LocationInfo; import gw.lang.reflect.MethodInfoBuilder; import gw.lang.reflect.ParameterInfoBuilder; import gw.lang.reflect.PropertyInfoBuilder; import gw.lang.reflect.TypeSystem; import gw.lang.reflect.java.IAsmJavaClassInfo; import gw.lang.reflect.java.IJavaClassConstructor; import gw.lang.reflect.java.IJavaClassInfo; import gw.lang.reflect.java.JavaTypes; import gw.util.GosuExceptionUtil; import gw.util.concurrent.LockingLazyVar; import gw.xml.XmlElement; import gw.xml.XmlParseOptions; import gw.xml.XmlTypeInstance; import javax.xml.namespace.QName; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.Reader; import java.io.StringReader; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * IType for statically typed XmlElement types. */ public class XmlSchemaElementTypeData<T> extends XmlSchemaTypeData<T> implements IXmlSchemaElementTypeData<T>, ILocationAwareFeature { private static final String TYPEINSTANCE_PROPERTY_NAME = "$TypeInstance"; private static final String QNAME_PROPERTY_NAME = "$QNAME"; private final T _context; private final XmlSchemaResourceTypeLoaderBase<T> _typeLoader; private final String _typeName; private XmlSchemaType _xsdType; private final XmlSchemaElement _xsdElement; private final boolean _anonymousElement; private LockingLazyVar<IType> _superType = new LockingLazyVar<IType>() { @Override protected IType init() { if ( _superTypeData != null ) { return _superTypeData.getType(); } if ( _xsdElement.getSubstitutionGroup() != null ) { return XmlSchemaIndex.getGosuTypeBySchemaObject(_xsdElement.getSchemaIndex().getXmlSchemaElementByQName(_xsdElement.getSubstitutionGroup())); } return TypeSystem.get( XmlElement.class ); } }; private final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; private final XmlSchemaElementTypeData _superTypeData; public XmlSchemaElementTypeData( XmlSchemaResourceTypeLoaderBase<T> typeLoader, String typeName, XmlSchemaType xsdType, XmlSchemaElement xsdElement, boolean anonymousElement, T context, XmlSchemaIndex<T> schemaIndex, XmlSchemaElementTypeData superTypeData ) { super( schemaIndex ); _typeLoader = typeLoader; _typeName = typeName; _xsdType = xsdType; _xsdElement = xsdElement; _anonymousElement = anonymousElement; _context = context; _superTypeData = superTypeData; } public XmlSchemaResourceTypeLoaderBase getTypeProvider() { return _typeLoader; } public XmlSchemaElement getXsdElement() { return _xsdElement; } public XmlSchemaType getXsdType() { if ( _xsdType == null ) { _xsdType = XmlSchemaIndex.getSchemaTypeForElement( _xsdElement ); } return _xsdType; } @Override public boolean prefixSuperProperties() { return getSuperType().equals( TypeSystem.get( XmlElement.class ) ); } @Override public long getFingerprint() { return getSchemaIndex().getFingerprint(); } public List<IPropertyInfo> getDeclaredProperties() { // if ( _prefixSuperProperties ) { // add $ to super properties List<IPropertyInfo> props = new ArrayList<IPropertyInfo>(); final IType delegate = XmlSchemaIndex.getGosuTypeBySchemaObject( getXsdType() ); for ( IPropertyInfo prop : delegate.getTypeInfo().getProperties() ) { if ( XmlSchemaIndex.getSchemaIndexByType( prop.getOwnersType() ) == null ) { continue; // only copy properties generated by us - not the ones from Object, etc } if ( prop.getName().equals( QNAME_PROPERTY_NAME ) ) { continue; // we add our own } props.add( new PropertyInfoBuilder().like( prop ).build( this ) ); } // add static typing to "TypeInstance" property IPropertyInfo typeInstanceProperty = TypeSystem.get( XmlElement.class ).getTypeInfo().getProperty( "TypeInstance" ); props.add( new PropertyInfoBuilder() .like( typeInstanceProperty ) .withLocation( _xsdType.getLocationInfo() ) .withName( TYPEINSTANCE_PROPERTY_NAME ) .withType( getXmlTypeInstanceTypeData().getType() ) .build( this ) ); props.add( new PropertyInfoBuilder() .withLocation( getLocationInfo() ) .withName( QNAME_PROPERTY_NAME ) .withType( JavaTypes.QNAME() ) .withDescription( "The QName of this element" ) .withWritable( false ) .withStatic() .withAccessor( new IPropertyAccessor() { @Override public Object getValue( Object ctx ) { QName qname = getXsdElement().getQName(); if ( qname == null ) { qname = getXsdElement().getRefName(); } return qname; } @Override public void setValue( Object ctx, Object value ) { throw new UnsupportedOperationException(); } } ) .build( this ) ); props.addAll( _typeLoader.getAdditionalProperties( this ) ); return props; } public List<IMethodInfo> getDeclaredMethods() { List<IMethodInfo> methods = new ArrayList<IMethodInfo>(); // parse(String) IMethodCallHandler callHandler = new IMethodCallHandler() { public Object handleCall( Object ctx, Object... args ) { final String string = (String) args[0]; if ( string.length() > 0 && string.indexOf( '<' ) < 0 ) { throw new RuntimeException( "Please use " + _typeName + ".parse( java.io.File ) to parse a file" ); } return XmlElementInternals.instance().parse( getType(), new StringReader( string ), "string", true, null, args.length > 1 ? (XmlParseOptions) args[1] : null ); } }; methods.add( makeParseMethod() .withCallHandler( callHandler ) .withParameters( new ParameterInfoBuilder() .withName( "xmlString" ) .withDescription( "The string to parse" ) .withType( TypeSystem.get( String.class ) ) ).build( this ) ); methods.add( makeParseMethod() .withCallHandler( callHandler ) .withParameters( new ParameterInfoBuilder() .withName( "xmlString" ) .withDescription( "The string to parse" ) .withType( TypeSystem.get( String.class ) ), new ParameterInfoBuilder() .withName( "options" ) .withDescription( "XML parse options" ) .withType( TypeSystem.get( XmlParseOptions.class ) ) ).build( this ) ); // parse(File) callHandler = new IMethodCallHandler() { public Object handleCall( Object ctx, Object... args ) { try { File file = (File) args[0]; FileInputStream stream = new FileInputStream( file ); try { return XmlElementInternals.instance().parse( getType(), stream, file.getCanonicalPath(), true, null, args.length > 1 ? (XmlParseOptions) args[1] : null, file.toURI().toURL().toExternalForm() ); } finally { try { stream.close(); } catch ( Exception ex ) { // ignore } } } catch ( Exception ex ) { throw GosuExceptionUtil.forceThrow( ex ); } } }; methods.add( makeParseMethod() .withCallHandler( callHandler ) .withParameters( new ParameterInfoBuilder() .withName( "file" ) .withDescription( "The file to parse" ) .withType( TypeSystem.get( File.class ) ) ).build( this ) ); methods.add( makeParseMethod() .withCallHandler( callHandler ) .withParameters( new ParameterInfoBuilder() .withName( "file" ) .withDescription( "The file to parse" ) .withType( TypeSystem.get( File.class ) ), new ParameterInfoBuilder() .withName( "options" ) .withDescription( "XML parse options" ) .withType( TypeSystem.get( XmlParseOptions.class ) ) ).build( this ) ); // parse(URL) callHandler = new IMethodCallHandler() { public Object handleCall( Object ctx, Object... args ) { try { URL url = (URL) args[0]; // try { // return CommonServices.getFileSystem().getIFile(new File(url.toURI())); // } catch (URISyntaxException e) { // throw new RuntimeException(e); // } InputStream stream = CommonServices.getFileSystem().getIFile(url).openInputStream(); try { return XmlElementInternals.instance().parse( getType(), stream, url.toExternalForm(), true, null, args.length > 1 ? (XmlParseOptions) args[1] : null, url.toExternalForm() ); } finally { try { stream.close(); } catch ( Exception ex ) { // ignore } } } catch ( Exception ex ) { throw GosuExceptionUtil.forceThrow( ex ); } } }; methods.add( makeParseMethod() .withCallHandler( callHandler ) .withParameters( new ParameterInfoBuilder() .withName( "url" ) .withDescription( "The URL to parse" ) .withType( TypeSystem.get( URL.class ) ) ).build( this ) ); methods.add( makeParseMethod() .withCallHandler( callHandler ) .withParameters( new ParameterInfoBuilder() .withName( "url" ) .withDescription( "The URL to parse" ) .withType( TypeSystem.get( URL.class ) ), new ParameterInfoBuilder() .withName( "options" ) .withDescription( "XML parse options" ) .withType( TypeSystem.get( XmlParseOptions.class ) ) ).build( this ) ); // parse(InputStream) callHandler = new IMethodCallHandler() { public Object handleCall( Object ctx, Object... args ) { InputStream stream = (InputStream) args[0]; try { return XmlElementInternals.instance().parse( getType(), stream, "input stream", true, null, args.length > 1 ? (XmlParseOptions) args[1] : null, null ); } finally { try { stream.close(); } catch ( Exception ex ) { // ignore } } } }; methods.add( makeParseMethod() .withCallHandler( callHandler ) .withParameters( new ParameterInfoBuilder() .withName( "inputStream" ) .withDescription( "The input stream to parse" ) .withType( TypeSystem.get( InputStream.class ) ) ).build( this ) ); methods.add( makeParseMethod() .withCallHandler( callHandler ) .withParameters( new ParameterInfoBuilder() .withName( "inputStream" ) .withDescription( "The input stream to parse" ) .withType( TypeSystem.get( InputStream.class ) ), new ParameterInfoBuilder() .withName( "options" ) .withDescription( "XML parse options" ) .withType( TypeSystem.get( XmlParseOptions.class ) ) ).build( this ) ); // parse(Reader) callHandler = new IMethodCallHandler() { public Object handleCall( Object ctx, Object... args ) { Reader reader = (Reader) args[0]; try { return XmlElementInternals.instance().parse( getType(), reader, "input reader", true, null, args.length > 1 ? (XmlParseOptions) args[1] : null ); } finally { try { reader.close(); } catch ( Exception ex ) { // ignore } } } }; methods.add( makeParseMethod() .withCallHandler( callHandler ) .withParameters( new ParameterInfoBuilder() .withName( "reader" ) .withDescription( "The reader to parse" ) .withType( TypeSystem.get( Reader.class ) ) ).build( this ) ); methods.add( makeParseMethod() .withCallHandler( callHandler ) .withParameters( new ParameterInfoBuilder() .withName( "reader" ) .withDescription( "The reader to parse" ) .withType( TypeSystem.get( Reader.class ) ), new ParameterInfoBuilder() .withName( "options" ) .withDescription( "XML parse options" ) .withType( TypeSystem.get( XmlParseOptions.class ) ) ).build( this ) ); // parse(byte[]) callHandler = new IMethodCallHandler() { public Object handleCall( Object ctx, Object... args ) { return XmlElementInternals.instance().parse( getType(), new ByteArrayInputStream( (byte[]) args[0] ), "byte array", true, null, args.length > 1 ? (XmlParseOptions) args[1] : null, null ); } }; methods.add( makeParseMethod() .withCallHandler( callHandler ) .withParameters( new ParameterInfoBuilder() .withName( "byteArray" ) .withDescription( "The byte array to parse" ) .withType( TypeSystem.get( byte[].class ) ) ).build( this ) ); methods.add( makeParseMethod() .withCallHandler( callHandler ) .withParameters( new ParameterInfoBuilder() .withName( "byteArray" ) .withDescription( "The byte array to parse" ) .withType( TypeSystem.get( byte[].class ) ), new ParameterInfoBuilder() .withName( "options" ) .withDescription( "XML parse options" ) .withType( TypeSystem.get( XmlParseOptions.class ) ) ).build( this ) ); methods.addAll( _typeLoader.getAdditionalMethods( this ) ); return methods; } private MethodInfoBuilder makeParseMethod() { return new MethodInfoBuilder().withStatic().withName("parse").withReturnType( getType() ); } public List<IConstructorInfo> getDeclaredConstructors() { final XmlSchemaTypeInstanceTypeData xmlTypeInstanceTypeData = getXmlTypeInstanceTypeData(); final IType xmlTypeInstanceType = xmlTypeInstanceTypeData.getType(); final IConstructorHandler constructorHandler; final Class clazz; final Class typeInstanceClass; try { IJavaClassInfo generatedClass = getSchemaIndex().getGeneratedClass( getType().getName() ); clazz = generatedClass == null ? null : Class.forName( generatedClass.getName()); IJavaClassInfo generatedClassTypeInst = XmlSchemaIndex.getSchemaIndexByType( xmlTypeInstanceType ).getGeneratedClass( xmlTypeInstanceType.getName() ); typeInstanceClass = generatedClassTypeInst == null ? null : Class.forName( generatedClassTypeInst.getName() ); } catch (ClassNotFoundException e) { throw new RuntimeException( e ); } if ( clazz != null && typeInstanceClass == null ) { throw new RuntimeException( "Partial codegen schema graph detected\n" + "Missing Generated Class: " + xmlTypeInstanceType.getName() ); } if ( clazz != null ) { constructorHandler = new IConstructorHandler() { @Override public Object newInstance( Object... args ) { try { if ( args.length > 0 ) { return clazz.getConstructor( typeInstanceClass ).newInstance( args[0] ); } else { return clazz.newInstance(); } } catch ( Exception ex ) { throw new RuntimeException( ex ); } } }; } else { constructorHandler = new IConstructorHandler() { @Override public Object newInstance( Object... args ) { XmlTypeInstance xmlTypeInstance = null; if ( args.length > 0 ) { xmlTypeInstance = (XmlTypeInstance) args[0]; } if ( xmlTypeInstance == null ) { IJavaClassInfo clazz = xmlTypeInstanceTypeData.getSchemaObject().getSchemaIndex().getGeneratedClass( xmlTypeInstanceType.getName() ); if ( clazz != null ) { try { xmlTypeInstance = (XmlTypeInstance) clazz.newInstance(); } catch ( Exception ex ) { throw GosuExceptionUtil.forceThrow( ex ); } } else { xmlTypeInstance = XmlTypeInstanceInternals.instance().create( xmlTypeInstanceType, xmlTypeInstanceTypeData.getSchemaInfo(), EMPTY_OBJECT_ARRAY ); } } return XmlElementInternals.instance().create( getXsdElement().getQName(), getType(), xmlTypeInstanceType, xmlTypeInstance ); } }; } List<IConstructorInfo> constructors = new ArrayList<IConstructorInfo>(); constructors.add( new ConstructorInfoBuilder().withConstructorHandler( constructorHandler ).build( this ) ); constructors.add( new ConstructorInfoBuilder().withConstructorHandler( constructorHandler ).withParameters( new ParameterInfoBuilder().withName( "typeInstance" ).withDescription( "The backing type instance for this element" ).withType( xmlTypeInstanceType ) ).build( this ) ); constructors.addAll( _typeLoader.getAdditionalConstructors( this ) ); return constructors; } private IJavaClassConstructor getDeclaredConstructor(IJavaClassInfo type, IJavaClassInfo paramType) { for (IJavaClassConstructor c : type.getDeclaredConstructors()) { if (c.getParameterTypes().length == 1 && c.getParameterTypes()[0].equals(paramType)) { return c; } } return null; } @Override public boolean isFinal() { return true; } @Override public boolean isEnum() { return false; } @Override public IType getSuperType() { return _superType.get(); } @Override public List<Class<?>> getAdditionalInterfaces() { //noinspection unchecked return Arrays.asList( IXmlSchemaElementTypeData.class, ILocationAwareFeature.class ); } public XmlSchemaTypeSchemaInfo getSchemaInfo() { XmlSchemaTypeInstanceTypeData typeInstanceTypeData = getXmlTypeInstanceTypeData(); return typeInstanceTypeData.getSchemaInfo(); } public XmlSchemaTypeInstanceTypeData getXmlTypeInstanceTypeData() { return (XmlSchemaTypeInstanceTypeData) XmlSchemaIndex.getGosuTypeDataBySchemaObject( getXsdType() ); } @Override public String getName() { return _typeName; } @Override public XmlSchemaObject getSchemaObject() { return _xsdElement; } @Override public boolean isAnonymous() { return _anonymousElement; } public T getContext() { return _context; } @Override public void maybeInit() { // nothing to do } @Override public Class getBackingClass() { IJavaClassInfo clazz = getSchemaIndex().getGeneratedClass( getType().getName() ); return (clazz != null && !(clazz instanceof IAsmJavaClassInfo)) ? clazz.getBackingClass() : XmlElement.class; } @Override public IJavaClassInfo getBackingClassInfo() { IJavaClassInfo clazz = getSchemaIndex().getGeneratedClass( getType().getName() ); return clazz != null ? clazz : JavaTypes.getSystemType(XmlElement.class).getBackingClassInfo(); } @Override public LocationInfo getLocationInfo() { return _xsdElement.getLocationInfo(); } }