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

com.yahoo.vespa.DocumentGenMojo Maven / Gradle / Ivy

There is a newer version: 8.444.18
Show newest version
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa;

import com.yahoo.collections.Pair;
import com.yahoo.document.ArrayDataType;
import com.yahoo.document.CollectionDataType;
import com.yahoo.document.DataType;
import com.yahoo.document.Field;
import com.yahoo.document.MapDataType;
import com.yahoo.document.PositionDataType;
import com.yahoo.document.StructDataType;
import com.yahoo.document.StructuredDataType;
import com.yahoo.document.TensorDataType;
import com.yahoo.document.WeightedSetDataType;
import com.yahoo.document.annotation.AnnotationReferenceDataType;
import com.yahoo.document.annotation.AnnotationType;
import com.yahoo.documentmodel.NewDocumentReferenceDataType;
import com.yahoo.documentmodel.NewDocumentType;
import com.yahoo.documentmodel.OwnedStructDataType;
import com.yahoo.documentmodel.VespaDocumentType;
import com.yahoo.schema.ApplicationBuilder;
import com.yahoo.schema.Schema;
import com.yahoo.schema.document.FieldSet;
import com.yahoo.schema.parser.ParseException;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Generates Vespa document classes from schema files.
 *
 * @author Vegard Balgaard Havdal
 */
@Mojo(name = "document-gen", defaultPhase = LifecyclePhase.GENERATE_SOURCES)
public class DocumentGenMojo extends AbstractMojo {

    private long newestModifiedTime = 0;

    private static final int STD_INDENT = 4;

    @Parameter(defaultValue = "${project}", readonly = true)
    private MavenProject project;

    /**
     * Directory containing the schema files
     */
    @Parameter(defaultValue = ".", required = true)
    private File schemasDirectory;

    /**
     * Java package for generated classes
     */
    @Parameter(defaultValue = "com.yahoo.vespa.document", required = true)
    private String packageName;

    /**
     * User provided annotation types that the plugin should not generate types for. They should however be included in
     * ConcreteDocumentFactory.
     */
    @Parameter
    private List provided = new ArrayList();

    /**
     * Annotation types for which the generated class should be abstract
     */
    @Parameter
    private List abztract = new ArrayList();

    /**
     * Generate source to here.
     */
    @Parameter(
            property = "plugin.configuration.outputDirectory",
            defaultValue="${project.build.directory}/generated-sources/vespa-documentgen-plugin/",
            required = true)
    private File outputDirectory;

    private Map searches;
    private Map docTypes;
    private Map structTypes;
    private Map annotationTypes;

    void execute(File schemasDir, File outputDir, String packageName) {
        if ("".equals(packageName)) throw new IllegalArgumentException("You may not use empty package for generated types.");
        searches = new HashMap<>();
        docTypes = new HashMap<>();
        structTypes = new HashMap<>();
        annotationTypes = new HashMap<>();

        outputDir.mkdirs();
        ApplicationBuilder builder = buildSearches(schemasDir);

        boolean annotationsExported=false;
        for (NewDocumentType docType : builder.getModel().getDocumentManager().getTypes()) {
            if ( docType != VespaDocumentType.INSTANCE) {
                exportDocumentSources(outputDir, docType, packageName);

                for (AnnotationType annotationType : docType.getAllAnnotations()) {
                    if (provided(annotationType.getName())!=null) continue;
                    annotationsExported=true;
                    exportAnnotationSources(outputDir, annotationType, docType, packageName);
                }
            }
        }
        exportPackageInfo(outputDir, packageName);
        if (annotationsExported) exportPackageInfo(outputDir, packageName+".annotation");
        exportDocFactory(outputDir, packageName);
        if (project!=null) project.addCompileSourceRoot(outputDirectory.toString());
    }

    private ApplicationBuilder buildSearches(File sdDir) {
        File[] sdFiles = sdDir.listFiles((dir, name) -> name.endsWith(".sd"));
        ApplicationBuilder builder = new ApplicationBuilder(true);
        for (File f : sdFiles) {
            try {
                long modTime = f.lastModified();
                if (modTime > newestModifiedTime) {
                    newestModifiedTime = modTime;
                }
                builder.addSchemaFile(f.getAbsolutePath());
            } catch (ParseException | IOException e) {
                throw new IllegalArgumentException(e);
            }
        }
        builder.build(true);
        for (Schema schema : builder.getSchemaList() ) {
            this.searches.put(schema.getName(), schema);
        }
        return builder;
    }

    /**
     * Creates package-info.java, so that the package of the concrete doc types get exported from user's bundle.
     */
    private void exportPackageInfo(File outputDir, String packageName) {
        File dirForSources = new File(outputDir, packageName.replaceAll("\\.", "/"));
        dirForSources.mkdirs();
        File target = new File(dirForSources, "package-info.java");
        if (target.lastModified() > newestModifiedTime) {
            getLog().debug("No changes, not updating "+target);
            return;
        }
        try (Writer out = new FileWriter(target)) {
                out.write("@ExportPackage\n" +
                    "package "+packageName+";\n\n" +
                    "import com.yahoo.osgi.annotation.ExportPackage;\n");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * The class for this type if provided, otherwise null
     */
    private String provided(String annotationType) {
        if (provided==null) return null;
        for (Annotation a : provided) {
            if (a.type.equals(annotationType)) return a.clazz;
        }
        return null;
    }

    private boolean isAbstract(String annotationType) {
        if (abztract==null) return false;
        for (Annotation a : abztract) {
            if (a.type.equals(annotationType)) return true;
        }
        return false;
    }

    private void exportDocFactory(File outputDir, String packageName) {
        File dirForSources = new File(outputDir, packageName.replaceAll("\\.", "/"));
        dirForSources.mkdirs();
        File target = new File(dirForSources, "ConcreteDocumentFactory.java");
        if (target.lastModified() > newestModifiedTime) {
            getLog().debug("No changes, not updating "+target);
            return;
        }
        try (Writer out = new FileWriter(target)) {
                out.write("package "+packageName+";\n\n" +
                    "/**\n" +
                    " *  Registry of generated concrete document, struct and annotation types.\n" +
                    " *\n" +
                    " *  Generated by vespa-documentgen-plugin, do not edit.\n" +
                    " *  Date: "+new Date()+"\n" +
                    " */\n");
            out.write("@com.yahoo.document.Generated\n");
            out.write("public class ConcreteDocumentFactory extends com.yahoo.docproc.AbstractConcreteDocumentFactory {\n");
            out.write(ind()+"private static java.util.Map> dTypes = new java.util.HashMap>();\n");
            out.write(ind()+"private static java.util.Map docTypes = new java.util.HashMap<>();\n");
            out.write(ind()+"private static java.util.Map> sTypes = new java.util.HashMap>();\n");
            out.write(ind()+"private static java.util.Map> aTypes = new java.util.HashMap>();\n");
            out.write(ind()+"static {\n");
            for (Map.Entry e : docTypes.entrySet()) {
                String docTypeName = e.getKey();
                String fullClassName = e.getValue();
                out.write(ind(2)+"dTypes.put(\""+docTypeName+"\","+fullClassName+".class);\n");
            }
            for (Map.Entry e : docTypes.entrySet()) {
                String docTypeName = e.getKey();
                String fullClassName = e.getValue();
                out.write(ind(2)+"docTypes.put(\""+docTypeName+"\","+fullClassName+".type);\n");
            }
            for (Map.Entry e : structTypes.entrySet()) {
                String structTypeName = e.getKey();
                String fullClassName = e.getValue();
                out.write(ind(2)+"sTypes.put(\""+structTypeName+"\","+fullClassName+".class);\n");
            }
            for (Map.Entry e : annotationTypes.entrySet()) {
                String annotationTypeName = e.getKey();
                String fullClassName = e.getValue();
                out.write(ind(2)+"aTypes.put(\""+annotationTypeName+"\","+fullClassName+".class);\n");
            }
            for (Annotation a : provided) {
                out.write(ind(2)+"aTypes.put(\""+a.type+"\","+a.clazz+".class);\n");
            }
            out.write(ind()+"}\n\n");

            out.write(
                    ind()+"public static final java.util.Map> documentTypes = java.util.Collections.unmodifiableMap(dTypes);\n" +
                    ind()+"public static final java.util.Map documentTypeObjects = java.util.Collections.unmodifiableMap(docTypes);\n" +
                    ind()+"public static final java.util.Map> structTypes = java.util.Collections.unmodifiableMap(sTypes);\n" +
                    ind()+"public static final java.util.Map> annotationTypes = java.util.Collections.unmodifiableMap(aTypes);\n\n");
            out.write(
                    ind()+"@Override public java.util.Map> documentTypes() { return ConcreteDocumentFactory.documentTypes; }\n" +
                    ind()+"@Override public java.util.Map> structTypes() { return ConcreteDocumentFactory.structTypes; }\n" +
                    ind()+"@Override public java.util.Map> annotationTypes() { return ConcreteDocumentFactory.annotationTypes; }\n\n");

            out.write(
                    ind()+"/**\n" +
                    ind()+" * Returns a new Document which will be of the correct concrete type and a copy of the given StructFieldValue.\n" +
                    ind()+" */\n" +
                    ind()+"@Override public com.yahoo.document.Document getDocumentCopy(java.lang.String type, com.yahoo.document.datatypes.StructuredFieldValue src, com.yahoo.document.DocumentId id) {\n");
                    for (Map.Entry e : docTypes.entrySet()) {
                        out.write(ind(2)+"if (\""+e.getKey()+"\".equals(type)) return new "+e.getValue()+"(src, id);\n");
                    }
                    out.write(ind(2)+"throw new java.lang.IllegalArgumentException(\"No such concrete document type: \"+type);\n");
                    out.write(ind()+"}\n\n");

            // delete, bad to use reflection?
            out.write(
                    ind()+"/**\n" +
                    ind()+" * Returns a new Document which will be of the correct concrete type.\n" +
                    ind()+" */\n" +
                    ind()+"public static com.yahoo.document.Document getDocument(java.lang.String type, com.yahoo.document.DocumentId id) {\n" +
                    ind(2)+"if (!ConcreteDocumentFactory.documentTypes.containsKey(type)) throw new java.lang.IllegalArgumentException(\"No such concrete document type: \"+type);\n" +
                    ind(2)+"try {\n" +
                    ind(3)+"return ConcreteDocumentFactory.documentTypes.get(type).getConstructor(com.yahoo.document.DocumentId.class).newInstance(id);\n" +
                    ind(2)+"} catch (java.lang.Exception ex) { throw new java.lang.RuntimeException(ex); }\n" +
                    ind()+"}\n\n");

            // delete, bad to use reflection?
            out.write(
                    ind()+"/**\n" +
                    ind()+" * Returns a new Struct which will be of the correct concrete type.\n" +
                    ind()+" */\n" +
                    ind()+"public static com.yahoo.document.datatypes.Struct getStruct(java.lang.String type) {\n" +
                    ind(2)+"if (!ConcreteDocumentFactory.structTypes.containsKey(type)) throw new java.lang.IllegalArgumentException(\"No such concrete struct type: \"+type);\n" +
                    ind(2)+"try {\n" +
                    ind(3)+"return ConcreteDocumentFactory.structTypes.get(type).getConstructor().newInstance();\n" +
                    ind(2)+"} catch (java.lang.Exception ex) { throw new java.lang.RuntimeException(ex); }\n" +
                    ind()+"}\n\n");

            // delete, bad to use reflection?
            out.write(
                    ind()+"/**\n" +
                    ind()+" * Returns a new Annotation which will be of the correct concrete type.\n" +
                    ind()+" */\n" +
                    ind()+"public static com.yahoo.document.annotation.Annotation getAnnotation(java.lang.String type) {\n" +
                    ind(2)+"if (!ConcreteDocumentFactory.annotationTypes.containsKey(type)) throw new java.lang.IllegalArgumentException(\"No such concrete annotation type: \"+type);\n" +
                    ind(2)+"try {\n" +
                    ind(3)+"return ConcreteDocumentFactory.annotationTypes.get(type).getConstructor().newInstance();\n" +
                    ind(2)+"} catch (java.lang.Exception ex) { throw new java.lang.RuntimeException(ex); }\n" +
                    ind()+"}\n\n");

            out.write("}\n");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void exportAnnotationSources(File outputDir, AnnotationType annType, NewDocumentType docType, String packageName) {
        File dirForSources = new File(outputDir, packageName.replaceAll("\\.", "/")+"/annotation/");
        dirForSources.mkdirs();
        String className = className(annType.getName());
        File target = new File(dirForSources, className+".java");
        if (target.lastModified() > newestModifiedTime) {
            getLog().debug("No changes, not updating "+target);
            return;
        }
        try (Writer out = new FileWriter(target)) {
                out.write("package "+packageName+".annotation;\n\n" +
                    "import "+packageName+".ConcreteDocumentFactory;\n" +
                    exportInnerImportsFromDocAndSuperTypes(docType, packageName) +
                    exportImportProvidedAnnotationRefs(annType) +
                    "/**\n" +
                    " *  Generated by vespa-documentgen-plugin, do not edit.\n" +
                    " *  Input annotation type: "+annType.getName()+"\n" +
                    " *  Date: "+new Date()+"\n" +
                    " */\n" +
                    "@com.yahoo.document.Generated\n" +
                    "public "+annTypeModifier(annType)+"class "+className+" extends "+getParentAnnotationType(annType)+" {\n\n");
            if (annType.getDataType() instanceof StructDataType) {
                out.write(ind() + "public "+className+"() {\n" +
                    ind(2) + "setType(new com.yahoo.document.annotation.AnnotationType(\""+annType.getName()+"\", Fields.type));\n" +
                    ind()+"}\n\n");
                StructDataType annStruct = (StructDataType)annType.getDataType();
                StructDataType annStructTmp = new StructDataType("fields"); // Change the type name
                annStructTmp.assign(annStruct);
                Collection tmpList = new ArrayList<>();
                tmpList.add(annStructTmp);
                exportStructTypes(tmpList, out, 1, null);
                exportFieldsAndAccessors(className, annStruct.getFieldsThisTypeOnly(), out, 1, false);
                out.write(ind()+"@Override public boolean hasFieldValue() { return true; }\n\n");
                out.write(ind()+"@Override public com.yahoo.document.datatypes.FieldValue getFieldValue() {\n");
                out.write(ind(2)+"com.yahoo.document.datatypes.Struct ret = new Fields();\n");

                for (Field f : annStruct.getFields()) {
                    out.write(
                            ind(2)+"if ("+getter(f.getName()) +"()!=null) {\n" +
                            ind(3)+"com.yahoo.document.Field f = ret.getField(\"" + f.getName() + "\");\n" +
                            ind(3)+"com.yahoo.document.datatypes.FieldValue fv = f.getDataType().createFieldValue(" + getter(f.getName()) + "());\n" +
                            ind(3)+"ret.setFieldValue(f, fv);\n" +
                            ind(2)+"}\n");
                }
                out.write(ind(2)+"return ret;\n");
                out.write(ind()+"}\n\n");

            } else {
                out.write(ind() + "public "+className+"() { setType(new com.yahoo.document.annotation.AnnotationType(\""+annType.getName()+"\")); } \n\n");
                out.write(ind()+"@Override public boolean hasFieldValue() { return false; }\n\n");
                out.write(ind()+"@Override public com.yahoo.document.datatypes.FieldValue getFieldValue() { return null; }\n\n");
            }
            out.write("}\n");
            annotationTypes.put(annType.getName(), packageName+".annotation."+className);
        } catch (IOException e) {
            throw new RuntimeException("Could not export sources for annotation type '"+annType.getName()+"'", e);
        }
    }

    /**
     * Handle the case of an annotation reference with a type that is user provided, we need to know the class name then
     */
    private String exportImportProvidedAnnotationRefs(AnnotationType annType) {
        if (annType.getDataType()==null) return "";
        StringBuilder ret = new StringBuilder();
        for (Field f : ((StructDataType)annType.getDataType()).getFields()) {
            if (f.getDataType() instanceof AnnotationReferenceDataType) {
                AnnotationReferenceDataType refType = (AnnotationReferenceDataType) f.getDataType();
                AnnotationType referenced = refType.getAnnotationType();
                String providedClass = provided(referenced.getName());
                if (providedClass!=null) {
                    // Annotationreference is to a type that is user-provided
                    ret.append("import ").append(providedClass).append(";\n");
                }
            }
        }
        return ret.toString();
    }

    private String annTypeModifier(AnnotationType annType) {
        if (isAbstract(annType.getName())) return "abstract ";
        return "";
    }

    private static String exportInnerImportsFromDocAndSuperTypes(NewDocumentType docType, String packageName) {
        String ret = "";
        ret = ret + "import "+packageName+"."+className(docType.getName())+".*;\n";
        ret = ret + exportInnerImportsFromSuperTypes(docType, packageName);
        return ret;
    }

    private static String exportInnerImportsFromSuperTypes(NewDocumentType docType, String packageName) {
        StringBuilder ret = new StringBuilder();
        for (NewDocumentType inherited : docType.getInherited()) {
            if (inherited.getName().equals("document")) continue;
            ret.append("import ").append(packageName).append(".").append(className(inherited.getName())).append(".*;\n");
        }
        return ret.toString();
    }

    private String getParentAnnotationType(AnnotationType annType) {
        if (annType.getInheritedTypes().isEmpty()) return "com.yahoo.document.annotation.Annotation";
        String superType = annType.getInheritedTypes().iterator().next().getName();
        String providedSuperType = provided(superType);
        if (providedSuperType!=null) return providedSuperType;
        return className(annType.getInheritedTypes().iterator().next().getName());
    }

    private void exportDocumentSources(File outputDir, NewDocumentType docType, String packageName) {
        File dirForSources = new File(outputDir, packageName.replaceAll("\\.", "/"));
        dirForSources.mkdirs();
        File target = new File(dirForSources, className(docType.getName())+".java");
        if (target.lastModified() > newestModifiedTime) {
            getLog().debug("No changes, not updating "+target);
            return;
        }
        try (Writer doc = new FileWriter(target)) {
            exportDocumentClass(docType, doc, packageName);
        } catch (IOException e) {
            throw new RuntimeException("Could not export sources for document type '"+docType.getName()+"'", e);
        }
    }

    /**
     * Generates the .java file for the Document subclass for the given document type
     */
    private void exportDocumentClass(NewDocumentType docType, Writer out, String packageName) throws IOException {
        String className = className(docType.getName());
        Pair extendInfo = javaSuperType(docType);
        String superType = extendInfo.getFirst();
        Boolean multiExtends = extendInfo.getSecond();
        out.write(
                "package "+packageName+";\n\n" +
                exportInnerImportsFromSuperTypes(docType, packageName) +
                "/**\n" +
                " *  Generated by vespa-documentgen-plugin, do not edit.\n" +
                " *  Input document type: "+docType.getName()+"\n" +
                " *  Date: "+new Date()+"\n" +
                " */\n" +
                "@com.yahoo.document.Generated\n" +
                "@SuppressWarnings(\"unchecked\")\n" +
                "public class "+className+" extends "+superType+" {\n\n"+
                ind(1)+"/** The doc type of this.*/\n" +
                ind(1)+"public static final com.yahoo.document.DocumentType type = getDocumentType();\n\n");

        // Constructor
        out.write(
                ind(1)+"public "+className+"(com.yahoo.document.DocumentId docId) {\n" +
                ind(2)+"super("+className+".type, docId);\n" +
                ind(1)+"}\n\n");
        out.write(
                ind(1)+"protected "+className+"(com.yahoo.document.DocumentType type, com.yahoo.document.DocumentId docId) {\n" +
                ind(2)+"super(type, docId);\n" +
                ind(1)+"}\n\n");
        // isGenerated()
        out.write(ind(1)+"@Override protected boolean isGenerated() { return true; }\n\n");

        Collection allUniqueFields = getAllUniqueFields(multiExtends, docType.getAllFields());
        exportExtendedStructTypeGetter(className, docType.getName(), docType.getInherited(), allUniqueFields, docType.getFieldSets(),
                docType.getImportedFieldNames(), out, 1, "getDocumentType", "com.yahoo.document.DocumentType");
        exportCopyConstructor(className, out, 1, true);

        exportFieldsAndAccessors(className, "com.yahoo.document.Document".equals(superType) ? allUniqueFields : docType.getFields(), out, 1, true);
        exportDocumentMethods(allUniqueFields, out, 1);
        exportHashCode(allUniqueFields, out, 1, "(getDataType() != null ? getDataType().hashCode() : 0) + getId().hashCode()");
        exportEquals(className, allUniqueFields, out, 1);
        Set exportedStructs = exportStructTypes(docType.getTypes(), out, 1, null);
        if (hasAnyPositionField(allUniqueFields)) {
            exportedStructs = exportStructTypes(List.of(PositionDataType.INSTANCE), out, 1, exportedStructs);
        }
        docTypes.put(docType.getName(), packageName+"."+className);
        for (DataType exportedStruct : exportedStructs) {
            String fullName = packageName+"."+className+"."+className(exportedStruct.getName());
            structTypes.put(exportedStruct.getName(), fullName);
            if (exportedStruct instanceof OwnedStructDataType) {
                var owned = (OwnedStructDataType) exportedStruct;
                structTypes.put(owned.getUniqueName(), fullName);
            }
        }
        out.write("}\n");
    }

    private static boolean hasAnyPositionDataType(DataType dt) {
        if (PositionDataType.INSTANCE.equals(dt)) {
            return true;
        } else if (dt instanceof CollectionDataType) {
            return hasAnyPositionDataType(((CollectionDataType)dt).getNestedType());
        } else if (dt instanceof StructuredDataType) {
            return hasAnyPositionField(((StructuredDataType)dt).getFields());
        } else {
            return false;
        }
    }

    private static boolean hasAnyPositionField(Collection fields) {
        for (Field f : fields) {
            if (hasAnyPositionDataType(f.getDataType())) {
                return true;
            }
        }
        return false;
    }

    private Collection getAllUniqueFields(Boolean multipleInheritance, Collection allFields) {
        if (multipleInheritance) {
            Map seen = new HashMap<>();
            List unique = new ArrayList<>(allFields.size());
            for (Field f : allFields) {
                if (seen.containsKey(f.getName())) {
                    if ( ! f.equals(seen.get(f.getName()))) {
                        throw new IllegalArgumentException("Field '" + f.getName() + "' has conflicting definitions in multiple inheritance." +
                                "First defined as '" + seen.get(f.getName()) + "', then as '" + f + "'.");
                    }
                } else {
                    unique.add(f);
                    seen.put(f.getName(), f);
                }
            }
            return unique;
        }
        return allFields;
    }

    /**
     * The Java class the class of the given type should inherit from. If the input type inherits from _one_
     * other type, use that, otherwise Document.
     */
    private static Pair javaSuperType(NewDocumentType docType) {
        String ret = "com.yahoo.document.Document";
        Collection specInheriteds = specificInheriteds(docType);
        boolean singleExtends = singleInheritance(specInheriteds);
        if (!specInheriteds.isEmpty() && singleExtends) ret = className(specInheriteds.iterator().next().getName());
        return new Pair<>(ret, !singleExtends);
    }

    private static boolean singleInheritance(Collection specInheriteds) {
        if (specInheriteds.isEmpty()) return true;
        if (specInheriteds.size()>1) return false;
        return singleInheritance(specificInheriteds(specInheriteds.iterator().next()));
    }

    /**
     * The inherited types that are not Document
     * @return collection of specific inherited types
     */
    private static Collection specificInheriteds(NewDocumentType type) {
        List ret = new ArrayList<>();
        for (NewDocumentType t : type.getInherited()) {
            if (!"document".equals(t.getName())) ret.add(t);
        }
        return ret;
    }

    /**
     * Exports the copy constructor.
     *
     * NOTE: This is important, the docproc framework uses that constructor.
     */
    private static void exportCopyConstructor(String className, Writer out, int ind, boolean docId) throws IOException {
        out.write(
                ind(ind)+"/**\n"+
                ind(ind)+" * Constructs a "+className+" by taking a deep copy of the provided StructuredFieldValue.\n" +
                ind(ind)+" */\n");
        if (docId) {
            out.write(ind(ind)+"public "+className+"(com.yahoo.document.datatypes.StructuredFieldValue src, com.yahoo.document.DocumentId docId) {\n"+
                    ind(ind+1)+"super("+className+".type,docId);\n");
        } else {
            out.write(ind(ind)+"public "+className+"(com.yahoo.document.datatypes.StructuredFieldValue src) {\n"+
                    ind(ind+1)+"super("+className+".type);\n");
        }
        out.write(ind(ind+1) + "ConcreteDocumentFactory factory = new ConcreteDocumentFactory();\n");
        out.write(
                ind(ind+1)+"for (java.util.Iterator>i=src.iterator() ; i.hasNext() ; ) {\n" +
                ind(ind+2)+"java.util.Map.Entry e = i.next();\n" +
                ind(ind+2)+"com.yahoo.document.Field f = e.getKey();\n" +
                ind(ind+2)+"com.yahoo.document.datatypes.FieldValue fv = e.getValue();\n" +
                ind(ind+2)+"setFieldValue(f, factory.optionallyUpgrade(f, fv));\n" +
                ind(ind+1)+"}\n"+
                ind(ind)+"}\n\n");
    }

    private static void addExtendedField(String className, Field f, Writer out, int ind) throws IOException {
        out.write(ind(ind)+ "ret.addField(new com.yahoo.document.ExtendedField(\""+f.getName()+"\", " + toJavaReference(f.getDataType()) + ",\n");
        out.write(ind(ind+1) + "new com.yahoo.document.ExtendedField.Extract() {\n");
        out.write(ind(ind+2) + "public Object get(com.yahoo.document.datatypes.StructuredFieldValue doc) {return ((" + className + ")doc)." + getter(f.getName()) + "(); }\n");
        out.write(ind(ind+2) + "public void set(com.yahoo.document.datatypes.StructuredFieldValue doc, Object value) { ((" + className + ")doc)." + setter(f.getName())+"((" + toJavaType(f.getDataType()) + ")value); }\n");
        out.write(ind(ind+1) + "}\n");
        out.write(ind(ind) + "));\n");
    }
    private static void addExtendedStringField(String className, Field f, Writer out, int ind) throws IOException {
        out.write(ind(ind)+ "ret.addField(new com.yahoo.document.ExtendedStringField(\""+f.getName()+"\", " + toJavaReference(f.getDataType()) + ",\n");
        out.write(ind(ind+1) + "new com.yahoo.document.ExtendedField.Extract() {\n");
        out.write(ind(ind+2) + "public Object get(com.yahoo.document.datatypes.StructuredFieldValue doc) {return ((" + className + ")doc)." + getter(f.getName()) + "(); }\n");
        out.write(ind(ind+2) + "public void set(com.yahoo.document.datatypes.StructuredFieldValue doc, Object value) { ((" + className + ")doc)." + setter(f.getName())+"((" + toJavaType(f.getDataType()) + ")value); }\n");
        out.write(ind(ind+1) + "},\n");
        out.write(ind(ind+1) + "new com.yahoo.document.ExtendedStringField.ExtractSpanTrees() {\n");
        out.write(ind(ind+2) + "public java.util.Map get(com.yahoo.document.datatypes.StructuredFieldValue doc) {return ((" + className + ")doc)." + spanTreeGetter(f.getName()) + "(); }\n");
        out.write(ind(ind+2) + "public void set(com.yahoo.document.datatypes.StructuredFieldValue doc, java.util.Map value) { ((" + className + ")doc)." + spanTreeSetter(f.getName()) + "(value); }\n");
        out.write(ind(ind+1) + "}\n");
        out.write(ind(ind) + "));\n");
    }
    private static void exportFieldSetDefinition(Set
fieldSets, Writer out, int ind) throws IOException { out.write(ind(ind) + "java.util.Map> fieldSets = new java.util.HashMap<>();\n"); for (FieldSet fieldSet : fieldSets) { out.write(ind(ind) + "fieldSets.put(\"" + fieldSet.getName() + "\", java.util.List.of("); int count = 0; for (String field : fieldSet.getFieldNames()) { out.write("\"" + field + "\""); if (++count != fieldSet.getFieldNames().size()) { out.write(","); } } out.write("));\n"); } out.write(ind(ind) + "ret.addFieldSets(fieldSets);\n"); } private static void exportImportedFields(Set importedFieldNames, Writer out, int ind) throws IOException { out.write(ind(ind) + "java.util.Set importedFieldNames = new java.util.HashSet<>();\n"); for (String importedField : importedFieldNames) { out.write(ind(ind) + "importedFieldNames.add(\"" + importedField + "\");\n"); } } private static void exportExtendedStructTypeGetter(String className, String name, Collection parentTypes, Collection fields, Set
fieldSets, Set importedFieldNames, Writer out, int ind, String methodName, String retType) throws IOException { out.write(ind(ind)+"private static "+retType+" "+methodName+"() {\n"); String bodyIndent = ind(ind + 1); if (importedFieldNames != null) { exportImportedFields(importedFieldNames, out, ind + 1); out.write(bodyIndent+retType+" ret = new "+retType+"(\"" + name + "\", importedFieldNames);\n"); } else { out.write(bodyIndent+retType+" ret = new "+retType+"(\""+name+"\");\n"); } for (Field f : fields) { if (f.getDataType().equals(DataType.STRING)) { addExtendedStringField(className, f, out, ind + 1); } else { addExtendedField(className, f, out, ind + 1); } } if (fieldSets != null) { exportFieldSetDefinition(fieldSets, out, ind+1); } for (NewDocumentType parentType : parentTypes) { if (!parentType.getName().equals("document")) { out.write("%sret.inherit(%s.type);\n".formatted(bodyIndent, className(parentType.getName()))); } } out.write(bodyIndent+"return ret;\n"); out.write(ind(ind)+"}\n\n"); } /** * Exports the necessary overridden methods from Document/StructuredFieldValue */ private static void exportDocumentMethods(Collection fieldSet, Writer out, int ind) throws IOException { exportGetFieldCount(fieldSet, out, ind); exportGetField(out, ind); exportGetFieldValue(fieldSet, out, ind); exportSetFieldValue(out, ind); exportRemoveFieldValue(out, ind); exportIterator(fieldSet, out, ind); exportClear(fieldSet, out, ind); } private static void exportEquals(String className, Collection fieldSet, Writer out, int ind) throws IOException { out.write(ind(ind)+"@Override public boolean equals(Object o) {\n"); out.write(ind(ind+1)+"if (!(o instanceof "+className+")) return false;\n"); out.write(ind(ind+1)+className+" other = ("+className+")o;\n"); for (Field field: fieldSet) { out.write(ind(ind+1)+"if ("+getter(field.getName())+"()==null) { if (other."+getter(field.getName())+"()!=null) return false; }\n"); out.write(ind(ind+2)+"else if (!("+getter(field.getName())+"().equals(other."+getter(field.getName())+"()))) return false;\n"); } out.write(ind(ind+1)+"return true;\n"); out.write(ind(ind)+"}\n\n"); } private static void exportHashCode(Collection fieldSet, Writer out, int ind, String hcBase) throws IOException { out.write(ind(ind)+"@Override public int hashCode() {\n"); out.write(ind(ind+1)+"int hc = "+hcBase+";\n"); for (Field field: fieldSet) { out.write(ind(ind+1)+"hc += "+getter(field.getName())+"()!=null ? "+getter(field.getName())+"().hashCode() : 0;\n"); } out.write(ind(ind+1)+"return hc;\n"); out.write(ind(ind)+"}\n\n"); } private static void exportClear(Collection fieldSet, Writer out, int ind) throws IOException { out.write(ind(ind)+"@Override public void clear() {\n"); for (Field field: fieldSet) { out.write(ind(ind+1)+setter(field.getName())+"(null);\n"); } out.write(ind(ind)+"}\n\n"); } private static void exportIterator(Collection fieldSet, Writer out, int ind) throws IOException { out.write(ind(ind)+"@Override public java.util.Iterator> iterator() {\n"); out.write(ind(ind+1)+"java.util.Map ret = new java.util.HashMap<>();\n"); for (Field field: fieldSet) { String name = field.getName(); out.write(ind(ind+1)+"if ("+getter(name)+"()!=null) {\n"); out.write(ind(ind+2)+"com.yahoo.document.Field f = getField(\""+name+"\");\n"); out.write(ind(ind+2)+"ret.put(f, ((com.yahoo.document.ExtendedField)f).getFieldValue(this));\n" + ind(ind+1)+"}\n"); } out.write(ind(ind+1)+"return ret.entrySet().iterator();\n" + ind(ind)+"}\n\n"); } private static void exportRemoveFieldValue(Writer out, int ind) throws IOException { out.write(ind(ind) + "@Override public com.yahoo.document.datatypes.FieldValue removeFieldValue(com.yahoo.document.Field field) {\n"); out.write(ind(ind+1) + "if (field==null) return null;\n"); out.write(ind(ind+1) + "com.yahoo.document.ExtendedField ef = ensureExtended(field);\n"); out.write(ind(ind+1) + "return (ef != null) ? ef.setFieldValue(this, null) : super.removeFieldValue(field);\n"); out.write(ind(ind) + "}\n"); } private static void exportSetFieldValue(Writer out, int ind) throws IOException { out.write(ind(ind)+"@Override public com.yahoo.document.datatypes.FieldValue setFieldValue(com.yahoo.document.Field field, com.yahoo.document.datatypes.FieldValue value) {\n"); out.write(ind(ind+1)+"com.yahoo.document.ExtendedField ef = ensureExtended(field);\n"); out.write(ind(ind+1)+"return (ef != null) ? ef.setFieldValue(this, value) : super.setFieldValue(field, value);\n"); out.write(ind(ind)+"}\n\n"); } private static void exportGetFieldValue(Collection fieldSet, Writer out, int ind) throws IOException { out.write(ind(ind)+"@Override public com.yahoo.document.datatypes.FieldValue getFieldValue(com.yahoo.document.Field field) {\n"); out.write(ind(ind+1)+"if (field==null) return null;\n"); out.write(ind(ind+1)+"if (field.getDataType() instanceof com.yahoo.document.StructDataType) {\n"); for (Field field: fieldSet) { String name = field.getName(); if (field.getDataType() instanceof StructDataType) { out.write(ind(ind+2)+"if (\""+name+"\".equals(field.getName())) return "+name+";\n"); } } out.write(ind(ind+1)+"}\n"); out.write(ind(ind+1)+"com.yahoo.document.ExtendedField ef = ensureExtended(field);\n"); out.write(ind(ind+1)+"return (ef != null) ? ef.getFieldValue(this) : super.getFieldValue(field);\n"); out.write(ind(ind)+"}\n\n"); } private static void exportGetField(Writer out, int ind) throws IOException { out.write(ind(ind) + "private com.yahoo.document.ExtendedStringField ensureExtendedString(com.yahoo.document.Field f) {\n"); out.write(ind(ind+1) + "return (com.yahoo.document.ExtendedStringField)((f instanceof com.yahoo.document.ExtendedStringField) ? f : getField(f.getName()));\n"); out.write(ind(ind) + "}\n\n"); out.write(ind(ind) + "private com.yahoo.document.ExtendedField ensureExtended(com.yahoo.document.Field f) {\n"); out.write(ind(ind+1) + "return (com.yahoo.document.ExtendedField)((f instanceof com.yahoo.document.ExtendedField) ? f : getField(f.getName()));\n"); out.write(ind(ind) + "}\n\n"); out.write(ind(ind) + "@Override public com.yahoo.document.Field getField(String fieldName) {\n"); out.write(ind(ind+1) + "if (fieldName==null) return null;\n"); out.write(ind(ind+1) + "return type.getField(fieldName);\n"); out.write(ind(ind) + "}\n\n"); } /** * Exports the struct types found in this collection of fields as separate Java classes */ private static Set exportStructTypes(Collection fields, Writer out, int ind, Set exportedStructs) throws IOException { if (exportedStructs==null) exportedStructs=new HashSet<>(); for (DataType f : fields) { if ((f instanceof StructDataType) && ! f.getName().contains(".")) { if (exportedStructs.contains(f)) continue; exportedStructs.add(f); StructDataType structType = (StructDataType) f; String structClassName = className(structType.getName()); out.write(ind(ind)+"/**\n" + ind(ind)+" * Generated by vespa-documentgen-plugin, do not edit.\n" + ind(ind)+" * Input struct type: "+structType.getName()+"\n" + ind(ind)+" * Date: "+new Date()+"\n" + ind(ind)+" */\n" + ind(ind)+"@com.yahoo.document.Generated\n" + ind(ind) + "public static class "+structClassName+" extends com.yahoo.document.datatypes.Struct {\n\n" + ind(ind+1)+"/** The type of this.*/\n" + ind(ind+1)+"public static final com.yahoo.document.StructDataType type = getStructType();\n\n"); out.write(ind(ind+1)+"public "+structClassName+"() {\n" + ind(ind+2)+"super("+structClassName+".type);\n" + ind(ind+1)+"}\n\n"); exportCopyConstructor(structClassName, out, ind+1, false); exportExtendedStructTypeGetter(structClassName, structType.getName(), List.of(), structType.getFields(), null, null, out, ind+1, "getStructType", "com.yahoo.document.StructDataType"); exportAssign(structType, structClassName, out, ind+1); exportFieldsAndAccessors(structClassName, structType.getFields(), out, ind+1, true); // Need these methods for serialization. // This can be improved by generating a method to serialize the struct _here_ (and one in the document), and use that in serialization. exportGetFields(structType.getFields(), out, ind+1); exportDocumentMethods(structType.getFields(), out, ind+1); exportHashCode(structType.getFields(), out, ind+1, "(getDataType() != null ? getDataType().hashCode() : 0)"); exportEquals(structClassName, structType.getFields(), out, ind+1); out.write(ind(ind)+"}\n\n"); } } return exportedStructs; } /** * Override this, serialization of structs relies on it */ private static void exportGetFieldCount(Collection fields, Writer out, int ind) throws IOException { out.write(ind(ind)+"@Override public int getFieldCount() {\n"); out.write(ind(ind+1)+"int ret=0;\n"); for (Field f : fields) { out.write(ind(ind+1)+"if ("+getter(f.getName())+"()!=null) ret++;\n"); } out.write(ind(ind+1)+"return ret;\n"); out.write(ind(ind)+"}\n\n"); } /** * Override the getFields() method of Struct, since serialization of Struct relies on it. */ private static void exportGetFields(Collection fields, Writer out, int ind) throws IOException { out.write(ind(ind)+"@Override public java.util.Set> getFields() {\n" + ind(ind+1)+"java.util.Map ret = new java.util.LinkedHashMap();\n"); for (Field f : fields) { out.write(ind(ind+1)+"if ("+getter(f.getName())+"()!=null) {\n"); out.write(ind(ind+2)+"com.yahoo.document.Field f = getField(\""+f.getName()+"\");\n"); out.write(ind(ind+2)+"ret.put(f, ((com.yahoo.document.ExtendedField)f).getFieldValue(this));\n"); out.write(ind(ind+1)+"}\n"); } out.write(ind(ind+1)+"return ret.entrySet();\n"); out.write(ind(ind)+"}\n\n"); } private static void exportAssign(StructDataType structType, String structClassName, Writer out, int ind) throws IOException { out.write(ind(ind)+"@Override public void assign(Object o) {\n"+ ind(ind+1)+"if (!(o instanceof "+structClassName+")) { super.assign(o); return; }\n"+ ind(ind+1)+structClassName+" other = ("+structClassName+")o;\n"); for (Field f: structType.getFields()) { out.write(ind(ind+1)+setter(f.getName())+"(other."+getter(f.getName())+"());\n"); } out.write(ind(ind)+"}\n\n"); } /** * Exports this set of fields with getters and setters * @param spanTrees If true, include a reference to the list of span trees for the string fields */ private static void exportFieldsAndAccessors(String className, Collection fields, Writer out, int ind, boolean spanTrees) throws IOException { // List the fields as Java fields for (Field field: fields) { DataType dt = field.getDataType(); out.write( ind(ind)+toJavaType(dt)+" "+field.getName()+";\n"); if (spanTrees && dt.equals(DataType.STRING)) { out.write(ind(ind)+"java.util.Map "+spanTreeGetter(field.getName())+";\n"); // same name on field as get method } } out.write(ind(ind)+"\n"); // Getters, setters and annotation spantree lists for string fields for (Field field: fields) { DataType dt = field.getDataType(); out.write( ind(ind) + "public " + toJavaType(dt) + " " + getter(field.getName()) + "() { return " + field.getName() + "; }\n" + ind(ind) + "public " + className + " " + setter(field.getName()) + "(" + toJavaType(dt) + " " + field.getName() + ") {\n" + validateArgument(field.getDataType(), field.getName(), ind + 1) + ind(ind+1) + "this." + field.getName() + "=" + field.getName() + ";\n" + ind(ind+1) + "return this;\n" + ind(ind) + "}\n"); if (spanTrees && dt.equals(DataType.STRING)) { out.write(ind(ind)+"public java.util.Map "+spanTreeGetter(field.getName())+"() { return "+field.getName()+"SpanTrees; }\n" + ind(ind)+"public void "+spanTreeSetter(field.getName())+"(java.util.Map spanTrees) { this."+field.getName()+"SpanTrees=spanTrees; }\n"); } } out.write("\n"); } private static String validateArgument(DataType type, String variable, int ind) { if (type instanceof MapDataType mdt) { return validateWrapped(mdt.getKeyType(), variable, variable + ".keySet()", ind) + validateWrapped(mdt.getValueType(), variable, variable + ".values()", ind); } else if (type instanceof CollectionDataType cdt) { String elements = cdt instanceof WeightedSetDataType ? variable + ".keySet()" : variable; return validateWrapped(cdt.getNestedType(), variable, elements, ind); } else if ( DataType.STRING.equals(type) || DataType.URI.equals(type) || type instanceof AnnotationReferenceDataType || type instanceof NewDocumentReferenceDataType) { return ind(ind) + "if (" + variable + " != null) {\n" + ind(ind+1) + toJavaReference(type) + ".createFieldValue(" + variable + ");\n" + ind(ind) + "}\n"; } else { return ""; } } private static String validateWrapped(DataType type, String variable, String elements, int ind) { String wrappedValidation = validateArgument(type, variable + "$", ind + 2); if (wrappedValidation.isBlank()) return ""; return ind(ind) + "if (" + variable + " != null) {\n" + ind(ind+1) + "for (" + toJavaType(type) + " " + variable + "$ : " + elements + ") {\n" + wrappedValidation + ind(ind+1) + "}\n" + ind(ind) + "}\n"; } private static String spanTreeSetter(String field) { return setter(field)+"SpanTrees"; } private static String spanTreeGetter(String field) { return field+"SpanTrees"; } /** * Returns spaces corresponding to the given levels of indentations */ private static String ind(int levels) { int indent = levels*STD_INDENT; StringBuilder sb = new StringBuilder(); for (int i = 0 ; i"; if (dt instanceof ArrayDataType adt) return "java.util.List<"+toJavaType(adt.getNestedType())+">"; if (dt instanceof MapDataType mdt) return "java.util.Map<"+toJavaType(mdt.getKeyType())+","+toJavaType((mdt).getValueType())+">"; if (dt instanceof AnnotationReferenceDataType ardt) return className(ardt.getAnnotationType().getName()); if (dt instanceof NewDocumentReferenceDataType) { return "com.yahoo.document.DocumentId"; } if (dt instanceof TensorDataType) { return "com.yahoo.tensor.Tensor"; } return "byte[]"; } // bit stupid... private static String toJavaReference(DataType dt) { if (DataType.NONE.equals(dt)) return "com.yahoo.document.DataType.NONE"; if (DataType.INT.equals(dt)) return "com.yahoo.document.DataType.INT"; if (DataType.FLOAT.equals(dt)) return "com.yahoo.document.DataType.FLOAT"; if (DataType.STRING.equals(dt)) return "com.yahoo.document.DataType.STRING"; if (DataType.RAW.equals(dt)) return "com.yahoo.document.DataType.RAW"; if (DataType.LONG.equals(dt)) return "com.yahoo.document.DataType.LONG"; if (DataType.DOUBLE.equals(dt)) return "com.yahoo.document.DataType.DOUBLE"; if (DataType.DOCUMENT.equals(dt)) return "com.yahoo.document.DataType.DOCUMENT"; if (DataType.URI.equals(dt)) return "com.yahoo.document.DataType.URI"; if (DataType.BYTE.equals(dt)) return "com.yahoo.document.DataType.BYTE"; if (DataType.BOOL.equals(dt)) return "com.yahoo.document.DataType.BOOL"; if (DataType.TAG.equals(dt)) return "com.yahoo.document.DataType.TAG"; if (dt instanceof StructDataType) return className(dt.getName()) +".type"; if (dt instanceof WeightedSetDataType wdt) return "new com.yahoo.document.WeightedSetDataType("+toJavaReference(wdt.getNestedType())+", "+ wdt.createIfNonExistent()+", "+ wdt.removeIfZero()+","+dt.getId()+")"; if (dt instanceof ArrayDataType adt) return "new com.yahoo.document.ArrayDataType("+toJavaReference(adt.getNestedType())+")"; if (dt instanceof MapDataType mdt) return "new com.yahoo.document.MapDataType("+toJavaReference(mdt.getKeyType())+", "+ toJavaReference(mdt.getValueType())+", "+dt.getId()+")"; // For annotation references and generated types, the references are to the actual objects of the correct types, so most likely this is never needed, // but there might be scenarios where we want to look up the AnnotationType in the AnnotationTypeRegistry here instead. if (dt instanceof AnnotationReferenceDataType adt) return "new com.yahoo.document.annotation.AnnotationReferenceDataType(new com.yahoo.document.annotation.AnnotationType(\""+adt.getAnnotationType().getName()+"\"))"; if (dt instanceof NewDocumentReferenceDataType nrdt) { // All concrete document types have a public `type` constant with their DocumentType. return String.format("new com.yahoo.document.ReferenceDataType(%s.type, %d)", className(nrdt.getTargetType().getName()), dt.getId()); } if (dt instanceof TensorDataType tdt) { return String.format("new com.yahoo.document.TensorDataType(com.yahoo.tensor.TensorType.fromSpec(\"%s\"))", tdt.getTensorType().toString()); } return "com.yahoo.document.DataType.RAW"; } @Override public void execute() { execute(this.schemasDirectory, this.outputDirectory, packageName); } Map getSearches() { return searches; } private static String upperCaseFirstChar(String s) { return s.substring(0, 1).toUpperCase()+s.substring(1); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy