/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package flex.messaging.io.amf.translator.decoder;

import java.util.Iterator;
import java.util.List;

import flex.messaging.util.ClassUtil;
import flex.messaging.io.PropertyProxy;
import flex.messaging.io.PropertyProxyRegistry;
import flex.messaging.io.TypeMarshallingContext;
import flex.messaging.io.amf.translator.TranslationException;

/**
 * Decodes an ASObject to a Java object based on the
 * type information returned from the ASObject.getType().
 * <p>
 * If the TranslationContext has been set up to support
 * _remoteClass then this property may be used as a back up.
 */
public class TypedObjectDecoder extends ActionScriptDecoder {
    public boolean hasShell() {
        return true;
    }

    public Object createShell(Object encodedObject, Class desiredClass) {
        Object shell = null;

        Class cls;
        String type = TypeMarshallingContext.getType(encodedObject);

        if (type != null) {
            TypeMarshallingContext context = TypeMarshallingContext.getTypeMarshallingContext();
            cls = ClassUtil.createClass(type, context.getClassLoader());
        } else {
            cls = desiredClass;
        }

        shell = ClassUtil.createDefaultInstance(cls, null);

        return shell;
    }

    public Object decodeObject(Object shell, Object encodedObject, Class desiredClass) {
        Object bean = shell;
        if (bean == null)
            return null;

        return decodeTypedObject(bean, encodedObject);
    }

    protected Object decodeTypedObject(Object bean, Object encodedObject) {
        PropertyProxy beanProxy = PropertyProxyRegistry.getProxyAndRegister(bean);
        PropertyProxy encodedProxy = PropertyProxyRegistry.getProxyAndRegister(encodedObject);

        List propertyNames = beanProxy.getPropertyNames(bean);
        if (propertyNames != null) {
            Iterator it = propertyNames.iterator();
            while (it.hasNext()) {
                String propName = (String) it.next();

                Class wClass = beanProxy.getType(bean, propName);

                // get property value from encodedObject
                Object value = encodedProxy.getValue(encodedObject, propName);

                Object decodedObject = null;
                try {
                    if (value != null) {
                        // We may need to honor our loose-typing rules for individual types as,
                        // unlike a Collection, an Array has a fixed element type. We'll use our handy
                        // decoder suite again to find us the right decoder...
                        ActionScriptDecoder decoder = DecoderFactory.getDecoder(value, wClass);
                        decodedObject = decoder.decodeObject(value, wClass);
                    }

                    // TODO: Perhaps we could update NumberDecoder, CharacterDecoder and
                    // BooleanDecoder to do this for us?
                    if (decodedObject == null && wClass.isPrimitive()) {
                        decodedObject = getDefaultPrimitiveValue(wClass);
                    }

                    beanProxy.setValue(bean, propName, decodedObject);
                } catch (Exception e) {
                    TranslationException ex = new TranslationException("Could not set object " + decodedObject + " on " + bean.getClass() + "'s " + propName);
                    ex.setCode("Server.Processing");
                    ex.setRootCause(e);
                    throw ex;
                }
            }
        }

        return bean;
    }
}
