All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.cxf.xjc.dv.DefaultValuePlugin Maven / Gradle / Ivy

The newest version!
/**
 * 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 org.apache.cxf.xjc.dv;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;

import org.xml.sax.ErrorHandler;

import com.sun.codemodel.ClassType;
import com.sun.codemodel.JBlock;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JConditional;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JDocComment;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JExpression;
import com.sun.codemodel.JFieldRef;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JInvocation;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JOp;
import com.sun.codemodel.JTryBlock;
import com.sun.codemodel.JType;
import com.sun.codemodel.JVar;
import com.sun.tools.xjc.BadCommandLineException;
import com.sun.tools.xjc.Options;
import com.sun.tools.xjc.outline.ClassOutline;
import com.sun.tools.xjc.outline.FieldOutline;
import com.sun.tools.xjc.outline.Outline;
import com.sun.tools.xjc.outline.PackageOutline;
import com.sun.tools.xjc.util.NamespaceContextAdapter;
import com.sun.xml.xsom.XSAttributeDecl;
import com.sun.xml.xsom.XSAttributeUse;
import com.sun.xml.xsom.XSElementDecl;
import com.sun.xml.xsom.XSParticle;
import com.sun.xml.xsom.XSTerm;
import com.sun.xml.xsom.XSType;
import com.sun.xml.xsom.XmlString;

import jakarta.xml.bind.DatatypeConverter;
import jakarta.xml.bind.annotation.adapters.HexBinaryAdapter;

/**
 * Modifies the JAXB code model to initialize fields mapped from schema elements 
 * with their default value.
 */
public class DefaultValuePlugin {

    private static final Logger LOG = Logger.getLogger(DefaultValuePlugin.class.getName()); //NOPMD

    // Known JAXB / JAXWS classes that do not have default constructors.
    private static final Set KNOWN_NO_DV_CLASSES = new HashSet<>(
            Arrays.asList(
                "jakarta.xml.ws.wsaddressing.W3CEndpointReference",
                "jakarta.xml.bind.JAXBElement"
            )
        );

    private boolean complexTypes;
    private boolean active;
    
    public DefaultValuePlugin() {
    }

    public String getOptionName() {
        return "Xdv";
    }

    public String getUsage() {
        return   "  -Xdv                 : Initialize fields mapped from elements with their default values\n"
               + "  -Xdv:optional        : Initialize fields mapped from elements with their default values\n"
               + "                         for elements with minOccurs=0 but with complexTypes containing \n"
               + "                         fields with default values.";
    }

    public int parseArgument(Options opt, String[] args, int index, com.sun.tools.xjc.Plugin plugin) 
        throws BadCommandLineException, IOException {
        int ret = 0;
        
        if (args[index].startsWith("-Xdv")) {
            ret = 1;                    
            if (args[index].indexOf(":optional") != -1) {
                complexTypes = true;
            }
            if (!opt.activePlugins.contains(plugin)) {
                opt.activePlugins.add(plugin);
            }
            active = true;
        }
        return ret;
    }

    private boolean isAbstract(Outline outline, FieldOutline field) {
        for (ClassOutline classOutline : outline.getClasses()) {
            if (classOutline.implClass == field.getRawType() 
                && classOutline.implClass.isAbstract()) {
                return true;
            }
        }
        return false;
    }
    
    private boolean containsDefaultValue(Outline outline, FieldOutline field) {
        ClassOutline fClass = null;
        for (ClassOutline classOutline : outline.getClasses()) {
            if (classOutline.implClass == field.getRawType() 
                && !classOutline.implClass.isAbstract()) {
                fClass = classOutline;
                break;
            }
        }
        if (fClass == null) {
            return false;
        }
        for (FieldOutline f : fClass.getDeclaredFields()) {
            if (f.getPropertyInfo().getSchemaComponent() instanceof XSParticle) {
                XSParticle particle = (XSParticle)f.getPropertyInfo().getSchemaComponent();
                XSTerm term = particle.getTerm();
                if (term.isElementDecl() && term.asElementDecl().getDefaultValue() != null) {
                    return true;
                }
            }
        }
        return false;
    }

    private boolean isElementRequired(XSParticle particle) {
        return particle != null && getMinOccurs(particle) != 0 && getMaxOccurs(particle) == 1;
    }

    private int getMinOccurs(XSParticle particle) {
        try {
            Number o = (Number)particle.getClass().getMethod("getMinOccurs").invoke(particle);
            return o.intValue(); 
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    private int getMaxOccurs(XSParticle particle) {
        try {
            Number o = (Number)particle.getClass().getMethod("getMaxOccurs").invoke(particle);
            return o.intValue(); 
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    public boolean run(Outline outline, Options opt, ErrorHandler errorHandler) {
        if (!active) {
            return true;
        }
        LOG.fine("Running default value plugin.");
        for (ClassOutline co : outline.getClasses()) {
            for (FieldOutline f : co.getDeclaredFields()) {

                // Use XML schema object model to determine if field is mapped
                // from an element (attributes default values are handled
                // natively) and get its default value.
                XmlString xmlDefaultValue = null;
                XSType xsType = null;
                boolean isElement = false;
                boolean isRequiredAttr = true;
                XSParticle particle = null;
                if (f.getPropertyInfo().getSchemaComponent() instanceof XSParticle) {
                    particle = (XSParticle)f.getPropertyInfo().getSchemaComponent();
                    XSTerm term = particle.getTerm();

                    if (term.isElementDecl()) {
                        XSElementDecl element = particle.getTerm().asElementDecl();
                        xmlDefaultValue = element.getDefaultValue();
                        xsType = element.getType();
                        isElement = true;
                    }
                } else if (f.getPropertyInfo().getSchemaComponent() instanceof XSAttributeUse) {
                    XSAttributeUse attributeUse = (XSAttributeUse)f.getPropertyInfo().getSchemaComponent();
                    XSAttributeDecl decl = attributeUse.getDecl();
                    xmlDefaultValue = decl.getDefaultValue();
                    xsType = decl.getType();
                    isRequiredAttr = attributeUse.isRequired();
                }

                if (xsType != null 
                    && xsType.isComplexType()
                    && !isAbstract(outline, f)
                    && ((complexTypes && containsDefaultValue(outline, f)) 
                        || isElementRequired(particle))) {
                    String varName = f.getPropertyInfo().getName(false);
                    JFieldVar var = co.implClass.fields().get(varName);
                    final JType rawType = f.getRawType();
                    if (var != null && !KNOWN_NO_DV_CLASSES.contains(rawType.erasure().fullName())) {
                        if (rawType instanceof JClass) {
                            final JClass jclazz = (JClass) rawType;
                            if (!jclazz.isAbstract() && !jclazz.isInterface()) {
                                var.init(JExpr._new(rawType));
                            }
                        } else {
                            var.init(JExpr._new(rawType));
                        }
                    }
                }

                JExpression dvExpr = null;
                if (null != xmlDefaultValue && null != xmlDefaultValue.value) {
                    dvExpr = getDefaultValueExpression(f, co, outline, xsType, isElement,
                                                       xmlDefaultValue, false);
                }
                 
                if (null == dvExpr
                    && !isElement 
                    && !isRequiredAttr
                    && xsType != null && xsType.getOwnerSchema() != null) {
                    //attribute, may still be able to convert it, but need to do
                    //a bunch more checks and changes to setters and isSet and such

                    dvExpr = 
                        getDefaultValueExpression(f, co, outline, xsType, false, 
                                                  xmlDefaultValue, true);
                    
                    if (dvExpr != null) {
                        updateSetter(co, f, co.implClass);
                        updateGetter(co, f, co.implClass, dvExpr, true);
                    } else {
                        JType type = f.getRawType();
                        String typeName = type.fullName();
                        if ("javax.xml.datatype.Duration".equals(typeName)) {
                            updateDurationGetter(co, f, co.implClass, xmlDefaultValue, outline);
                        }
                    }
                } else if (null == dvExpr) {                    
                    JType type = f.getRawType();
                    String typeName = type.fullName();
                    if ("javax.xml.datatype.Duration".equals(typeName)) {
                        updateDurationGetter(co, f, co.implClass, xmlDefaultValue, outline);
                    }
                } else {
                    updateGetter(co, f, co.implClass, dvExpr, false);                    
                }
            }
        }
        
        for (PackageOutline po :outline.getAllPackageContexts()) {
            //also fixup some unecessary casts
            JDefinedClass cls = po.objectFactoryGenerator().getObjectFactory();
            for (JMethod m : cls.methods()) {
                String tn = m.type().fullName();
                if (tn.startsWith("jakarta.xml.bind.JAXBElement")) {
                    JBlock b = m.body();
                    
                    for (Object o : b.getContents()) {
                        try {
                            Field f = o.getClass().getDeclaredField("expr");
                            f.setAccessible(true);
                            JInvocation ji = (JInvocation)f.get(o);
                            
                            f = JInvocation.class.getDeclaredField("args");
                            f.setAccessible(true);
                            @SuppressWarnings("unchecked")
                            List args = (List)f.get(ji);
                            
                            JExpression cast = args.get(args.size() - 1);
                            if ("JCast".equals(cast.getClass().getSimpleName())) {
                                f = cast.getClass().getDeclaredField("object");
                                f.setAccessible(true);
                                JExpression exp = (JExpression)f.get(cast);
                                args.remove(args.size() - 1);
                                args.add(exp);
                            }
                        } catch (Throwable t) {
                            //ignore
                        }
                    }
                }
            }
        }

        return true;
    }
    
    
    private void updateDurationGetter(ClassOutline co, FieldOutline fo, JDefinedClass dc,
                                      XmlString xmlDefaultValue, Outline outline) {
        String fieldName = fo.getPropertyInfo().getName(false);

        String getterName = "get" + fo.getPropertyInfo().getName(true);

        JMethod method = dc.getMethod(getterName, new JType[0]);
        JDocComment doc = method.javadoc();
        int mods = method.mods().getValue();
        JType mtype = method.type();

        if (LOG.isLoggable(Level.FINE)) {
            LOG.fine("Updating getter: " + getterName);
        }
        // remove existing method and define new one

        dc.methods().remove(method);

        method = dc.method(mods, mtype, getterName);
        method.javadoc().append(doc);

        JFieldRef fr = JExpr.ref(fieldName);
        if (xmlDefaultValue != null) {
            JExpression test = JOp.eq(JExpr._null(), fr);
            JConditional jc =  method.body()._if(test);
            JTryBlock b = jc._then()._try();
            b.body()._return(outline.getCodeModel().ref(DatatypeFactory.class)
                .staticInvoke("newInstance").invoke("newDuration").arg(JExpr.lit(xmlDefaultValue.value)));
            b._catch(outline.getCodeModel().ref(DatatypeConfigurationException.class));
            method.body()._return(fr);
        } else {
            method.body()._return(fr);
        }

    }

    JExpression getDefaultValueExpression(FieldOutline f,
                                          ClassOutline co,
                                          Outline outline,
                                          XSType xsType,
                                          boolean isElement,
                                          XmlString xmlDefaultValue,
                                          boolean unbox) {
        JType type = f.getRawType();
        String typeName = type.fullName();
        String defaultValue = xmlDefaultValue == null ? null : xmlDefaultValue.value;
        if (defaultValue == null) {
            return null;
        }

        JExpression dv = null;
        
        if ("java.lang.Boolean".equals(typeName) && isElement) {
            dv = JExpr.direct(Boolean.valueOf(defaultValue) ? "Boolean.TRUE" : "Boolean.FALSE");
        } else if ("java.lang.Byte".equals(typeName) && isElement) {
            dv = JExpr._new(type)
                .arg(JExpr.cast(type.unboxify(),
                    JExpr.lit(Byte.parseByte(defaultValue))));
        } else if ("java.lang.Double".equals(typeName) && isElement) {
            dv = JExpr._new(type)
                .arg(JExpr.lit(Double.parseDouble(defaultValue)));
        } else if ("java.lang.Float".equals(typeName) && isElement) {
            dv = JExpr._new(type)
                 .arg(JExpr.lit(Float.parseFloat(defaultValue)));
        } else if ("java.lang.Integer".equals(typeName) && isElement) {
            dv = JExpr._new(type)
                .arg(JExpr.lit(Integer.parseInt(defaultValue)));
        } else if ("java.lang.Long".equals(typeName) && isElement) {
            dv = JExpr._new(type)
                .arg(JExpr.lit(Long.parseLong(defaultValue)));
        } else if ("java.lang.Short".equals(typeName) && isElement) {
            dv = JExpr._new(type)
                .arg(JExpr.cast(type.unboxify(),
                    JExpr.lit(Short.parseShort(defaultValue))));
        } else if ("java.lang.String".equals(type.fullName()) && isElement) {
            dv = JExpr.lit(defaultValue);
        } else if ("java.math.BigInteger".equals(type.fullName()) && isElement) {
            dv = JExpr._new(type).arg(JExpr.lit(defaultValue));
        } else if ("java.math.BigDecimal".equals(type.fullName()) && isElement) {
            dv = JExpr._new(type).arg(JExpr.lit(defaultValue));
        } else if ("byte[]".equals(type.fullName()) && xsType.isSimpleType() && isElement) {
            while (!"anySimpleType".equals(xsType.getBaseType().getName())) {
                xsType = xsType.getBaseType();
            }
            if ("base64Binary".equals(xsType.getName())) {
                dv = outline.getCodeModel().ref(DatatypeConverter.class)
                   .staticInvoke("parseBase64Binary").arg(defaultValue);
            } else if ("hexBinary".equals(xsType.getName())) {
                dv = JExpr._new(outline.getCodeModel().ref(HexBinaryAdapter.class))
                    .invoke("unmarshal").arg(defaultValue);
            }
        } else if ("javax.xml.namespace.QName".equals(typeName)) {
            NamespaceContext nsc = new NamespaceContextAdapter(xmlDefaultValue);
            QName qn = DatatypeConverter.parseQName(xmlDefaultValue.value, nsc);
            dv = JExpr._new(outline.getCodeModel().ref(QName.class))
                .arg(qn.getNamespaceURI())
                .arg(qn.getLocalPart())
                .arg(qn.getPrefix());
        } else if ("javax.xml.datatype.Duration".equals(typeName)) {
            dv = null;
        } else if (type instanceof JDefinedClass) {
            JDefinedClass cls = (JDefinedClass)type;
            if (cls.getClassType() == ClassType.ENUM) {
                dv = cls.staticInvoke("fromValue").arg(defaultValue);
            }
        } else if (unbox) {
            typeName = type.unboxify().fullName();
            if ("int".equals(typeName)) {
                dv = JExpr.lit(Integer.parseInt(defaultValue));
            } else if ("long".equals(typeName)) {
                dv = JExpr.lit(Long.parseLong(defaultValue));
            } else if ("short".equals(typeName)) {
                dv = JExpr.lit(Short.parseShort(defaultValue));
            } else if ("boolean".equals(typeName)) {
                dv = JExpr.lit(Boolean.parseBoolean(defaultValue));
            } else if ("double".equals(typeName)) {
                dv = JExpr.lit(Double.parseDouble(defaultValue));
            } else if ("float".equals(typeName)) {
                dv = JExpr.lit(Float.parseFloat(defaultValue));
            } else if ("byte".equals(typeName)) {
                dv = JExpr.lit(Byte.parseByte(defaultValue));
            } else {
                dv = getDefaultValueExpression(f,
                                               co,
                                               outline,
                                               xsType,
                                               true,
                                               xmlDefaultValue,
                                               false);
            }
        }
        return dv;
    }
    
    private void updateGetter(ClassOutline co, FieldOutline fo, 
                              JDefinedClass dc, JExpression dvExpr,
                              boolean remapRet) {

        String fieldName = fo.getPropertyInfo().getName(false);
        JType type = fo.getRawType();
        String typeName = type.fullName();

        String getterName = ("java.lang.Boolean".equals(typeName) ? "is" : "get")
                            + fo.getPropertyInfo().getName(true);

        JMethod method = dc.getMethod(getterName, new JType[0]);
        JDocComment doc = method.javadoc();
        int mods = method.mods().getValue();
        JType mtype = method.type();
        if (remapRet) {
            mtype = mtype.unboxify();
        }

        if (LOG.isLoggable(Level.FINE)) {
            LOG.fine("Updating getter: " + getterName);
        }
        // remove existing method and define new one
        dc.methods().remove(method);

        method = dc.method(mods, mtype, getterName);
        method.javadoc().append(doc);

        JFieldRef fr = JExpr.ref(fieldName);
        if (dvExpr != null) {
            JExpression test = JOp.eq(JExpr._null(), fr);
            JConditional jc =  method.body()._if(test);
            jc._then()._return(dvExpr);
        }
        method.body()._return(fr);
    }
    private void updateSetter(ClassOutline co, FieldOutline fo, 
                              JDefinedClass dc) {

        String fieldName = fo.getPropertyInfo().getName(false);
        JType type = fo.getRawType();
        String typeName = type.fullName();

        String getterName = ("java.lang.Boolean".equals(typeName) ? "is" : "get")
                            + fo.getPropertyInfo().getName(true);
        JMethod method = dc.getMethod(getterName, new JType[0]);
        JType mtype = method.type();
        String setterName = "set" + fo.getPropertyInfo().getName(true);
        method = dc.getMethod(setterName, new JType[] {mtype});
        if (LOG.isLoggable(Level.FINE)) {
            LOG.fine("Updating setter: " + setterName);
        }
        if (method == null) {
            return;
        }
        JDocComment doc = method.javadoc();
        // remove existing method and define new one
        dc.methods().remove(method);

        int mods = method.mods().getValue();
        mtype = mtype.unboxify();
        method = dc.method(mods, method.type(), setterName);
        
        method.javadoc().append(doc);
        JVar var = method.param(mtype, "value");

        JFieldRef fr = JExpr.ref(fieldName);
        method.body().assign(fr, var);
        
        JMethod oldMethod = dc.getMethod("unset" + fo.getPropertyInfo().getName(true), new JType[0]);
        if (oldMethod != null) {
            dc.methods().remove(oldMethod);
        }
        method = dc.method(mods, method.type(), "unset" + fo.getPropertyInfo().getName(true));
        method.body().assign(fr, JExpr._null());
        
        method = dc.getMethod("isSet" + fo.getPropertyInfo().getName(true), new JType[0]);
        if (method != null) {
            //move to end
            dc.methods().remove(method);
            dc.methods().add(method);
        }
        
    }

    public void onActivated(Options opts) {
        active = true;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy