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

com.unboundid.ldap.sdk.persist.GenerateSourceFromSchema Maven / Gradle / Ivy

/*
 * Copyright 2009-2019 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright (C) 2009-2019 Ping Identity Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPLv2 only)
 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see .
 */
package com.unboundid.ldap.sdk.persist;



import java.io.File;
import java.io.FileWriter;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.TreeMap;
import java.util.TreeSet;

import com.unboundid.ldap.sdk.DN;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.Filter;
import com.unboundid.ldap.sdk.LDAPConnection;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPInterface;
import com.unboundid.ldap.sdk.ReadOnlyEntry;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.Version;
import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
import com.unboundid.ldap.sdk.schema.ObjectClassDefinition;
import com.unboundid.ldap.sdk.schema.ObjectClassType;
import com.unboundid.ldap.sdk.schema.Schema;
import com.unboundid.util.Debug;
import com.unboundid.util.LDAPCommandLineTool;
import com.unboundid.util.Mutable;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import com.unboundid.util.args.ArgumentException;
import com.unboundid.util.args.ArgumentParser;
import com.unboundid.util.args.BooleanArgument;
import com.unboundid.util.args.DNArgument;
import com.unboundid.util.args.FileArgument;
import com.unboundid.util.args.StringArgument;

import static com.unboundid.ldap.sdk.persist.PersistMessages.*;



/**
 * This class provides a tool which can be used to generate source code for a
 * Java class file based on information read from the schema of an LDAP
 * directory server.
 */
@Mutable()
@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
public final class GenerateSourceFromSchema
       extends LDAPCommandLineTool
       implements Serializable
{
  /**
   * The serial version UID for this serializable class.
   */
  private static final long serialVersionUID = 3488976364950590266L;



  /**
   * A pre-allocated empty tree set.
   */
  private static final TreeSet EMPTY_TREE_SET = new TreeSet<>();



  // Arguments used by this tool.
  private BooleanArgument terseArg;
  private DNArgument      defaultParentDNArg;
  private FileArgument    outputDirectoryArg;
  private StringArgument  auxiliaryClassArg;
  private StringArgument  classNameArg;
  private StringArgument  lazyAttributeArg;
  private StringArgument  operationalAttributeArg;
  private StringArgument  packageNameArg;
  private StringArgument  rdnAttributeArg;
  private StringArgument  structuralClassArg;

  // Indicates whether any multivalued attributes have been identified, and
  // therefore we need to include java.util.Arrays in the import list.
  private boolean needArrays;

  // Indicates whether any date attributes have been identified, and therefore
  // we need to include java.util.Date in the import list.
  private boolean needDate;

  // Indicates whether any DN-syntax attributes have been identified, and
  // therefore we need to include com.unboundid.ldap.sdk.DN in the import list.
  private boolean needDN;

  // Indicates whether
  // Indicates whether any DN-syntax attributes have been identified, and
  // therefore we need to include
  // com.unboundid.ldap.sdk.persist.PersistedObjects in the import list.
  private boolean needPersistedObjects;



  /**
   * Parse the provided command line arguments and perform the appropriate
   * processing.
   *
   * @param  args  The command line arguments provided to this program.
   */
  public static void main(final String[] args)
  {
    final ResultCode resultCode = main(args, System.out, System.err);
    if (resultCode != ResultCode.SUCCESS)
    {
      System.exit(resultCode.intValue());
    }
  }



  /**
   * Parse the provided command line arguments and perform the appropriate
   * processing.
   *
   * @param  args       The command line arguments provided to this program.
   * @param  outStream  The output stream to which standard out should be
   *                    written.  It may be {@code null} if output should be
   *                    suppressed.
   * @param  errStream  The output stream to which standard error should be
   *                    written.  It may be {@code null} if error messages
   *                    should be suppressed.
   *
   * @return  A result code indicating whether the processing was successful.
   */
  public static ResultCode main(final String[] args,
                                final OutputStream outStream,
                                final OutputStream errStream)
  {
    final GenerateSourceFromSchema tool =
         new GenerateSourceFromSchema(outStream, errStream);
    return tool.runTool(args);
  }



  /**
   * Creates a new instance of this tool.
   *
   * @param  outStream  The output stream to which standard out should be
   *                    written.  It may be {@code null} if output should be
   *                    suppressed.
   * @param  errStream  The output stream to which standard error should be
   *                    written.  It may be {@code null} if error messages
   *                    should be suppressed.
   */
  public GenerateSourceFromSchema(final OutputStream outStream,
                                  final OutputStream errStream)
  {
    super(outStream, errStream);

    needArrays           = false;
    needDate             = false;
    needDN               = false;
    needPersistedObjects = false;
  }



  /**
   * {@inheritDoc}
   */
  @Override()
  public String getToolName()
  {
    return "generate-source-from-schema";
  }



  /**
   * {@inheritDoc}
   */
  @Override()
  public String getToolDescription()
  {
    return INFO_GEN_SOURCE_TOOL_DESCRIPTION.get();
  }



  /**
   * Retrieves the version string for this tool.
   *
   * @return  The version string for this tool.
   */
  @Override()
  public String getToolVersion()
  {
    return Version.NUMERIC_VERSION_STRING;
  }



  /**
   * Indicates whether this tool should provide support for an interactive mode,
   * in which the tool offers a mode in which the arguments can be provided in
   * a text-driven menu rather than requiring them to be given on the command
   * line.  If interactive mode is supported, it may be invoked using the
   * "--interactive" argument.  Alternately, if interactive mode is supported
   * and {@link #defaultsToInteractiveMode()} returns {@code true}, then
   * interactive mode may be invoked by simply launching the tool without any
   * arguments.
   *
   * @return  {@code true} if this tool supports interactive mode, or
   *          {@code false} if not.
   */
  @Override()
  public boolean supportsInteractiveMode()
  {
    return true;
  }



  /**
   * Indicates whether this tool defaults to launching in interactive mode if
   * the tool is invoked without any command-line arguments.  This will only be
   * used if {@link #supportsInteractiveMode()} returns {@code true}.
   *
   * @return  {@code true} if this tool defaults to using interactive mode if
   *          launched without any command-line arguments, or {@code false} if
   *          not.
   */
  @Override()
  public boolean defaultsToInteractiveMode()
  {
    return true;
  }



  /**
   * Indicates whether this tool should provide arguments for redirecting output
   * to a file.  If this method returns {@code true}, then the tool will offer
   * an "--outputFile" argument that will specify the path to a file to which
   * all standard output and standard error content will be written, and it will
   * also offer a "--teeToStandardOut" argument that can only be used if the
   * "--outputFile" argument is present and will cause all output to be written
   * to both the specified output file and to standard output.
   *
   * @return  {@code true} if this tool should provide arguments for redirecting
   *          output to a file, or {@code false} if not.
   */
  @Override()
  protected boolean supportsOutputFile()
  {
    return true;
  }



  /**
   * Indicates whether this tool should default to interactively prompting for
   * the bind password if a password is required but no argument was provided
   * to indicate how to get the password.
   *
   * @return  {@code true} if this tool should default to interactively
   *          prompting for the bind password, or {@code false} if not.
   */
  @Override()
  protected boolean defaultToPromptForBindPassword()
  {
    return true;
  }



  /**
   * Indicates whether this tool supports the use of a properties file for
   * specifying default values for arguments that aren't specified on the
   * command line.
   *
   * @return  {@code true} if this tool supports the use of a properties file
   *          for specifying default values for arguments that aren't specified
   *          on the command line, or {@code false} if not.
   */
  @Override()
  public boolean supportsPropertiesFile()
  {
    return true;
  }



  /**
   * Indicates whether the LDAP-specific arguments should include alternate
   * versions of all long identifiers that consist of multiple words so that
   * they are available in both camelCase and dash-separated versions.
   *
   * @return  {@code true} if this tool should provide multiple versions of
   *          long identifiers for LDAP-specific arguments, or {@code false} if
   *          not.
   */
  @Override()
  protected boolean includeAlternateLongIdentifiers()
  {
    return true;
  }



  /**
   * Indicates whether this tool should provide a command-line argument that
   * allows for low-level SSL debugging.  If this returns {@code true}, then an
   * "--enableSSLDebugging}" argument will be added that sets the
   * "javax.net.debug" system property to "all" before attempting any
   * communication.
   *
   * @return  {@code true} if this tool should offer an "--enableSSLDebugging"
   *          argument, or {@code false} if not.
   */
  @Override()
  protected boolean supportsSSLDebugging()
  {
    return true;
  }



  /**
   * {@inheritDoc}
   */
  @Override()
  public void addNonLDAPArguments(final ArgumentParser parser)
         throws ArgumentException
  {
    outputDirectoryArg = new FileArgument('d', "outputDirectory", false, 1,
         INFO_GEN_SOURCE_VALUE_PLACEHOLDER_PATH.get(),
         INFO_GEN_SOURCE_ARG_DESCRIPTION_OUTPUT_DIRECTORY.get(), true, true,
         false, true);
    outputDirectoryArg.addLongIdentifier("output-directory", true);
    parser.addArgument(outputDirectoryArg);

    structuralClassArg = new StringArgument('s', "structuralClass", true, 1,
         INFO_GEN_SOURCE_VALUE_PLACEHOLDER_NAME.get(),
         INFO_GEN_SOURCE_ARG_DESCRIPTION_STRUCTURAL_CLASS.get());
    structuralClassArg.addLongIdentifier("structural-class", true);
    parser.addArgument(structuralClassArg);

    auxiliaryClassArg = new StringArgument('a', "auxiliaryClass", false, 0,
         INFO_GEN_SOURCE_VALUE_PLACEHOLDER_NAME.get(),
         INFO_GEN_SOURCE_ARG_DESCRIPTION_AUXILIARY_CLASS.get());
    auxiliaryClassArg.addLongIdentifier("auxiliary-class", true);
    parser.addArgument(auxiliaryClassArg);

    rdnAttributeArg = new StringArgument('r', "rdnAttribute", true, 0,
         INFO_GEN_SOURCE_VALUE_PLACEHOLDER_NAME.get(),
         INFO_GEN_SOURCE_ARG_DESCRIPTION_RDN_ATTRIBUTE.get());
    rdnAttributeArg.addLongIdentifier("rdn-attribute", true);
    parser.addArgument(rdnAttributeArg);

    lazyAttributeArg = new StringArgument('l', "lazyAttribute", false, 0,
         INFO_GEN_SOURCE_VALUE_PLACEHOLDER_NAME.get(),
         INFO_GEN_SOURCE_ARG_DESCRIPTION_LAZY_ATTRIBUTE.get());
    lazyAttributeArg.addLongIdentifier("lazy-attribute", true);
    parser.addArgument(lazyAttributeArg);

    operationalAttributeArg = new StringArgument('O', "operationalAttribute",
         false, 0, INFO_GEN_SOURCE_VALUE_PLACEHOLDER_NAME.get(),
         INFO_GEN_SOURCE_ARG_DESCRIPTION_OPERATIONAL_ATTRIBUTE.get());
    operationalAttributeArg.addLongIdentifier("operational-attribute", true);
    parser.addArgument(operationalAttributeArg);

    defaultParentDNArg = new DNArgument('b', "defaultParentDN", false, 1,
         INFO_GEN_SOURCE_VALUE_PLACEHOLDER_DN.get(),
         INFO_GEN_SOURCE_ARG_DESCRIPTION_DEFAULT_PARENT_DN.get());
    defaultParentDNArg.addLongIdentifier("default-parent-dn", true);
    parser.addArgument(defaultParentDNArg);

    packageNameArg = new StringArgument('n', "packageName", false, 1,
         INFO_GEN_SOURCE_VALUE_PLACEHOLDER_NAME.get(),
         INFO_GEN_SOURCE_ARG_DESCRIPTION_PACKAGE_NAME.get());
    packageNameArg.addLongIdentifier("package-name", true);
    parser.addArgument(packageNameArg);

    classNameArg = new StringArgument('c', "className", false, 1,
         INFO_GEN_SOURCE_VALUE_PLACEHOLDER_NAME.get(),
         INFO_GEN_SOURCE_ARG_DESCRIPTION_CLASS_NAME.get());
    classNameArg.addLongIdentifier("class-name", true);
    parser.addArgument(classNameArg);

    terseArg = new BooleanArgument('t', "terse", 1,
         INFO_GEN_SOURCE_ARG_DESCRIPTION_TERSE.get());
    parser.addArgument(terseArg);
  }



  /**
   * {@inheritDoc}
   */
  @Override()
  public ResultCode doToolProcessing()
  {
    // Establish a connection to the target directory server and retrieve the
    // schema.
    final LDAPConnection conn;
    try
    {
      conn = getConnection();
    }
    catch (final LDAPException le)
    {
      Debug.debugException(le);
      err(ERR_GEN_SOURCE_CANNOT_CONNECT.get(
           StaticUtils.getExceptionMessage(le)));
      return le.getResultCode();
    }

    final Schema schema;
    try
    {
      schema = conn.getSchema();
      if (schema == null)
      {
        err(ERR_GEN_SOURCE_CANNOT_READ_SCHEMA.get(
             ERR_GEN_SOURCE_SCHEMA_NOT_RETURNED.get()));
        return ResultCode.NO_RESULTS_RETURNED;
      }
    }
    catch (final LDAPException le)
    {
      Debug.debugException(le);
      err(ERR_GEN_SOURCE_CANNOT_READ_SCHEMA.get(
           StaticUtils.getExceptionMessage(le)));
      return le.getResultCode();
    }
    finally
    {
      conn.close();
    }

    return generateSourceFile(schema, terseArg.isPresent());
  }



  /**
   * Generates the source file using the information in the provided schema.
   *
   * @param  schema  The schema to use to generate the source file.
   * @param  terse   Indicates whether to use terse mode when generating the
   *                 source file.  If this is {@code true}, then all optional
   *                 elements will be omitted from annotations.
   *
   * @return  A result code obtained for the processing.
   */
  private ResultCode generateSourceFile(final Schema schema,
                                        final boolean terse)
  {
    // Retrieve and process the structural object class.
    final TreeMap requiredAttrs =
         new TreeMap<>();
    final TreeMap optionalAttrs =
         new TreeMap<>();
    final TreeMap> requiredAttrOCs = new TreeMap<>();
    final TreeMap> optionalAttrOCs = new TreeMap<>();
    final TreeMap types = new TreeMap<>();

    final String structuralClassName = structuralClassArg.getValue();
    final ObjectClassDefinition structuralOC =
         schema.getObjectClass(structuralClassName);
    if (structuralOC == null)
    {
      err(ERR_GEN_SOURCE_STRUCTURAL_CLASS_NOT_FOUND.get(structuralClassName));
      return ResultCode.PARAM_ERROR;
    }

    if (structuralOC.getObjectClassType(schema) != ObjectClassType.STRUCTURAL)
    {
      err(ERR_GEN_SOURCE_STRUCTURAL_CLASS_NOT_STRUCTURAL.get(
           structuralClassName));
      return ResultCode.PARAM_ERROR;
    }

    processObjectClass(structuralOC, schema, requiredAttrs, requiredAttrOCs,
         optionalAttrs, optionalAttrOCs, types);


    // Retrieve and process the auxiliary object classes.
    final TreeMap auxiliaryOCs = new TreeMap<>();
    if (auxiliaryClassArg.isPresent())
    {
      for (final String s : auxiliaryClassArg.getValues())
      {
        final ObjectClassDefinition oc = schema.getObjectClass(s);
        if (oc == null)
        {
          err(ERR_GEN_SOURCE_AUXILIARY_CLASS_NOT_FOUND.get(s));
          return ResultCode.PARAM_ERROR;
        }

        if  (oc.getObjectClassType(schema) != ObjectClassType.AUXILIARY)
        {
          err(ERR_GEN_SOURCE_AUXILIARY_CLASS_NOT_AUXILIARY.get(s));
          return ResultCode.PARAM_ERROR;
        }

        auxiliaryOCs.put(StaticUtils.toLowerCase(s), oc);

        processObjectClass(oc, schema, requiredAttrs, requiredAttrOCs,
             optionalAttrs, optionalAttrOCs, types);
      }
    }


    // Determine the appropriate set of superior object classes.
    final TreeMap superiorOCs = new TreeMap<>();
    for (final ObjectClassDefinition s :
         structuralOC.getSuperiorClasses(schema, true))
    {
      superiorOCs.put(StaticUtils.toLowerCase(s.getNameOrOID()), s);
    }

    for (final ObjectClassDefinition d : auxiliaryOCs.values())
    {
      for (final ObjectClassDefinition s : d.getSuperiorClasses(schema, true))
      {
        superiorOCs.put(StaticUtils.toLowerCase(s.getNameOrOID()), s);
      }
    }

    superiorOCs.remove(StaticUtils.toLowerCase(structuralClassName));
    for (final String s : auxiliaryOCs.keySet())
    {
      superiorOCs.remove(s);
    }


    // Retrieve and process the operational attributes.
    final TreeMap operationalAttrs =
         new TreeMap<>();
    if (operationalAttributeArg.isPresent())
    {
      for (final String s : operationalAttributeArg.getValues())
      {
        final AttributeTypeDefinition d = schema.getAttributeType(s);
        if (d == null)
        {
          err(ERR_GEN_SOURCE_OPERATIONAL_ATTRIBUTE_NOT_DEFINED.get(s));
          return ResultCode.PARAM_ERROR;
        }
        else if (! d.isOperational())
        {
          err(ERR_GEN_SOURCE_OPERATIONAL_ATTRIBUTE_NOT_OPERATIONAL.get(s));
          return ResultCode.PARAM_ERROR;
        }
        else
        {
          final String lowerName = StaticUtils.toLowerCase(s);
          operationalAttrs.put(lowerName, d);
          types.put(lowerName, getJavaType(schema, d));
        }
      }
    }


    // Make sure all of the configured RDN attributes are allowed by at least
    // one of the associated object classes.
    final TreeSet rdnAttrs = new TreeSet<>();
    for (final String s : rdnAttributeArg.getValues())
    {
      final AttributeTypeDefinition d = schema.getAttributeType(s);
      if (d == null)
      {
        err(ERR_GEN_SOURCE_RDN_ATTRIBUTE_NOT_DEFINED.get(s));
        return ResultCode.PARAM_ERROR;
      }

      final String lowerName = StaticUtils.toLowerCase(d.getNameOrOID());
      rdnAttrs.add(lowerName);
      if (requiredAttrs.containsKey(lowerName))
      {
        // No action required.
      }
      else if (optionalAttrs.containsKey(lowerName))
      {
        // Move the attribute to the required set.
        requiredAttrs.put(lowerName, optionalAttrs.remove(lowerName));
        requiredAttrOCs.put(lowerName, optionalAttrOCs.remove(lowerName));
      }
      else
      {
        err(ERR_GEN_SOURCE_RDN_ATTRIBUTE_NOT_DEFINED.get(s));
        return ResultCode.PARAM_ERROR;
      }
    }


    // Make sure all of the configured lazily-loaded attributes are allowed by
    // at least one of the associated object classes or matches a configured
    // operational attribute.
    final TreeSet lazyAttrs = new TreeSet<>();
    for (final String s : lazyAttributeArg.getValues())
    {
      final AttributeTypeDefinition d = schema.getAttributeType(s);
      if (d == null)
      {
        err(ERR_GEN_SOURCE_LAZY_ATTRIBUTE_NOT_DEFINED.get(s));
        return ResultCode.PARAM_ERROR;
      }

      final String lowerName = StaticUtils.toLowerCase(d.getNameOrOID());
      lazyAttrs.add(lowerName);
      if (requiredAttrs.containsKey(lowerName) ||
          optionalAttrs.containsKey(lowerName) ||
          operationalAttrs.containsKey(lowerName))
      {
        // No action required.
      }
      else
      {
        err(ERR_GEN_SOURCE_LAZY_ATTRIBUTE_NOT_ALLOWED.get(s));
        return ResultCode.PARAM_ERROR;
      }
    }


    final String className;
    if (classNameArg.isPresent())
    {
      className = classNameArg.getValue();
      final StringBuilder invalidReason = new StringBuilder();
      if (! PersistUtils.isValidJavaIdentifier(className, invalidReason))
      {
        err(ERR_GEN_SOURCE_INVALID_CLASS_NAME.get(className,
             invalidReason.toString()));
        return ResultCode.PARAM_ERROR;
      }
    }
    else
    {
      className = StaticUtils.capitalize(
           PersistUtils.toJavaIdentifier(structuralClassName));
    }


    final File sourceFile = new File(outputDirectoryArg.getValue(),
         className + ".java");
    final PrintWriter writer;
    try
    {
      writer = new PrintWriter(new FileWriter(sourceFile));
    }
    catch (final Exception e)
    {
      Debug.debugException(e);
      err(ERR_GEN_SOURCE_CANNOT_CREATE_WRITER.get(sourceFile.getAbsolutePath(),
           StaticUtils.getExceptionMessage(e)));
      return ResultCode.LOCAL_ERROR;
    }


    if (packageNameArg.isPresent())
    {
      final String packageName = packageNameArg.getValue();
      if (! packageName.isEmpty())
      {
        writer.println("package " + packageName + ';');
        writer.println();
        writer.println();
        writer.println();
      }
    }

    boolean javaImports = false;
    if (needArrays)
    {
      writer.println("import " + Arrays.class.getName() + ';');
      javaImports = true;
    }

    if (needDate)
    {
      writer.println("import " + Date.class.getName() + ';');
      javaImports = true;
    }

    if (javaImports)
    {
      writer.println();
    }

    if (needDN)
    {
      writer.println("import " + DN.class.getName() + ';');
    }

    writer.println("import " + Entry.class.getName() + ';');
    writer.println("import " + Filter.class.getName() + ';');

    if (needDN)
    {
      writer.println("import " + LDAPException.class.getName() + ';');
      writer.println("import " + LDAPInterface.class.getName() + ';');
    }

    writer.println("import " + ReadOnlyEntry.class.getName() + ';');
    writer.println("import " + DefaultObjectEncoder.class.getName() + ';');
    writer.println("import " + FieldInfo.class.getName() + ';');
    writer.println("import " + FilterUsage.class.getName() + ';');
    writer.println("import " + LDAPEntryField.class.getName() + ';');
    writer.println("import " + LDAPField.class.getName() + ';');
    writer.println("import " + LDAPObject.class.getName() + ';');
    writer.println("import " + LDAPObjectHandler.class.getName() + ';');
    writer.println("import " + LDAPPersister.class.getName() + ';');
    writer.println("import " + LDAPPersistException.class.getName() + ';');

    if (needPersistedObjects)
    {
      writer.println("import " + PersistedObjects.class.getName() + ';');
    }

    writer.println("import " + PersistFilterType.class.getName() + ';');

    if (needDN)
    {
      writer.println("import " + PersistUtils.class.getName() + ';');
    }

    writer.println();
    writer.println();
    writer.println();
    writer.println("/**");
    writer.println(" * This class provides an implementation of an object " +
         "that can be used to");
    writer.println(" * represent " + structuralClassName +
         " objects in the directory.");
    writer.println(" * It was generated by the " + getToolName() +
         " tool provided with the");
    writer.println(" * UnboundID LDAP SDK for Java.  It " +
         "may be customized as desired to better suit");
    writer.println(" * your needs.");
    writer.println(" */");
    writer.println("@LDAPObject(structuralClass=\"" + structuralClassName +
         "\",");

    switch (auxiliaryOCs.size())
    {
      case 0:
        // No action required.
        break;

      case 1:
        writer.println("            auxiliaryClass=\"" +
             auxiliaryOCs.values().iterator().next().getNameOrOID() + "\",");
        break;

      default:
        final Iterator iterator =
             auxiliaryOCs.values().iterator();
        writer.println("            auxiliaryClass={ \"" +
             iterator.next().getNameOrOID() + "\",");
        while (iterator.hasNext())
        {
          final String ocName = iterator.next().getNameOrOID();
          if (iterator.hasNext())
          {
            writer.println("                             \"" + ocName +
                 "\",");
          }
          else
          {
            writer.println("                             \"" + ocName +
                 "\" },");
          }
        }
        break;
    }

    switch (superiorOCs.size())
    {
      case 0:
        // No action required.
        break;

      case 1:
        writer.println("            superiorClass=\"" +
             superiorOCs.values().iterator().next().getNameOrOID() + "\",");
        break;

      default:
        final Iterator iterator =
             superiorOCs.values().iterator();
        writer.println("            superiorClass={ \"" +
             iterator.next().getNameOrOID() + "\",");
        while (iterator.hasNext())
        {
          final String ocName = iterator.next().getNameOrOID();
          if (iterator.hasNext())
          {
            writer.println("                             \"" + ocName +
                 "\",");
          }
          else
          {
            writer.println("                             \"" + ocName +
                 "\" },");
          }
        }
        break;
    }

    if (defaultParentDNArg.isPresent())
    {
      writer.println("            defaultParentDN=\"" +
           defaultParentDNArg.getValue() + "\",");
    }

    writer.println("            postDecodeMethod=\"doPostDecode\",");
    writer.println("            postEncodeMethod=\"doPostEncode\")");
    writer.println("public class " + className);
    writer.println("{");

    if (! terse)
    {
      writer.println("  /*");
      writer.println("   * NOTE:  This class includes a number of annotation " +
           "elements which are not");
      writer.println("   * required but have been provided to make it easier " +
           "to edit the resulting");
      writer.println("   * source code.  If you want to exclude these " +
           "unnecessary annotation");
      writer.println("   * elements, use the '--terse' command-line argument.");
      writer.println("   */");
      writer.println();
      writer.println();
      writer.println();
    }

    writer.println("  // The field to use to hold a read-only copy of the " +
         "associated entry.");
    writer.println("  @LDAPEntryField()");
    writer.println("  private ReadOnlyEntry ldapEntry;");


    // Add all of the fields.  First the fields for the RDN attributes, then
    // for the rest of the required attributes, then for the optional
    // attributes, and finally any operational attributes.
    for (final String lowerName : rdnAttrs)
    {
      final AttributeTypeDefinition d = requiredAttrs.get(lowerName);
      final TreeSet ocNames = requiredAttrOCs.get(lowerName);
      writeField(writer, d, types.get(lowerName), ocNames, true, true,
           structuralClassName, false, terse);
    }

    for (final String lowerName : requiredAttrs.keySet())
    {
      if (rdnAttrs.contains(lowerName))
      {
        continue;
      }

      final AttributeTypeDefinition d = requiredAttrs.get(lowerName);
      final TreeSet ocNames = requiredAttrOCs.get(lowerName);
      writeField(writer, d, types.get(lowerName), ocNames, false, true,
           structuralClassName, lazyAttrs.contains(lowerName), terse);
    }

    for (final String lowerName : optionalAttrs.keySet())
    {
      final AttributeTypeDefinition d = optionalAttrs.get(lowerName);
      final TreeSet ocNames = optionalAttrOCs.get(lowerName);
      writeField(writer, d, types.get(lowerName), ocNames, false, false,
           structuralClassName, lazyAttrs.contains(lowerName), terse);
    }

    for (final String lowerName : operationalAttrs.keySet())
    {
      final AttributeTypeDefinition d = operationalAttrs.get(lowerName);
      final TreeSet ocNames = EMPTY_TREE_SET;
      writeField(writer, d, types.get(lowerName), ocNames, false, false,
           structuralClassName, lazyAttrs.contains(lowerName), terse);
    }


    // Add the default constructor.
    writer.println();
    writer.println();
    writer.println();
    writer.println("  /**");
    writer.println("   * Creates a new instance of this object.  All fields " +
         "will be uninitialized,");
    writer.println("   * so the setter methods should be used to assign " +
         "values to them.");
    writer.println("   */");
    writer.println("  public " + className + "()");
    writer.println("  {");
    writer.println("    // No initialization will be performed by default.  " +
         "Note that if you set");
    writer.println("    // values for any fields marked with an @LDAPField, " +
         "@LDAPDNField, or");
    writer.println("    // @LDAPEntryField annotation, they will be " +
         "overwritten in the course of");
    writer.println("    // decoding initializing this object from an LDAP " +
         "entry.");
    writer.println("  }");


    // Add a static decode method that can create an instance of the object
    // from a given entry.
    writer.println();
    writer.println();
    writer.println();
    writer.println("  /**");
    writer.println("   * Creates a new " + className + " object decoded");
    writer.println("   * from the provided entry.");
    writer.println("   *");
    writer.println("   * @param  entry  The entry to be decoded.");
    writer.println("   *");
    writer.println("   * @return  The decoded " + className + " object.");
    writer.println("   *");
    writer.println("   * @throws  LDAPPersistException  If a problem occurs " +
         "while attempting to");
    writer.println("   *                                decode the provided " +
         "entry.");
    writer.println("   */");
    writer.println("  public static " + className +
         " decode(final Entry entry)");
    writer.println("         throws LDAPPersistException");
    writer.println("  {");
    writer.println("    return getPersister().decode(entry);");
    writer.println("  }");


    // Add the getPersister method.
    writer.println("");
    writer.println("");
    writer.println("");
    writer.println("  /**");
    writer.println("   * Retrieves an {@code LDAPPersister} instance that " +
         "may be used to interact");
    writer.println("   * with objects of this type.");
    writer.println("   *");
    writer.println("   * @return  An {@code LDAPPersister} instance that may " +
         "be used to interact");
    writer.println("   *          with objects of this type.");
    writer.println("   *");
    writer.println("   * @throws  LDAPPersistException  If a problem occurs " +
         "while creating the");
    writer.println("   *                                " +
         "{@code LDAPPersister} instance.");
    writer.println("   */");
    writer.println("  public static LDAPPersister<" + className +
         "> getPersister()");
    writer.println("         throws LDAPPersistException");
    writer.println("  {");
    writer.println("    return LDAPPersister.getInstance(" + className +
         ".class);");
    writer.println("  }");


    // Add the post-decode and post-encode methods.
    writer.println();
    writer.println();
    writer.println();
    writer.println("  /**");
    writer.println("   * Performs any processing that may be necessary after " +
         "initializing this");
    writer.println("   * object from an LDAP entry.");
    writer.println("   *");
    writer.println("   * @throws  LDAPPersistException  If there is a " +
         "problem with the object after");
    writer.println("   *                                it has been decoded " +
         "from an LDAP entry.");
    writer.println("   */");
    writer.println("  private void doPostDecode()");
    writer.println("          throws LDAPPersistException");
    writer.println("  {");
    writer.println("    // No processing is needed by default.  You may " +
         "provide an implementation");
    writer.println("    // for this method if custom post-decode processing " +
         "is needed.");
    writer.println("  }");
    writer.println();
    writer.println();
    writer.println();
    writer.println("  /**");
    writer.println("   * Performs any processing that may be necessary after " +
         "encoding this object");
    writer.println("   * to an LDAP entry.");
    writer.println("   *");
    writer.println("   * @param  entry  The entry that has been generated.  " +
         "It may be altered if");
    writer.println("   *                desired.");
    writer.println("   *");
    writer.println("   * @throws  LDAPPersistException  If the generated " +
         "entry should not be used.");
    writer.println("   */");
    writer.println("  private void doPostEncode(final Entry entry)");
    writer.println("          throws LDAPPersistException");
    writer.println("  {");
    writer.println("    // No processing is needed by default.  You may " +
         "provide an implementation");
    writer.println("    // for this method if custom post-encode processing " +
         "is needed.");
    writer.println("  }");


    // Add a method for getting a read-only copy of the associated entry.
    writer.println();
    writer.println();
    writer.println();
    writer.println("  /**");
    writer.println("   * Retrieves a read-only copy of the entry with which " +
         "this object is");
    writer.println("   * associated, if it is available.  It will only be " +
         "available if this object");
    writer.println("   * was decoded from or encoded to an LDAP entry.");
    writer.println("   *");
    writer.println("   * @return  A read-only copy of the entry with which " +
         "this object is");
    writer.println("   *          associated, or {@code null} if it is not " +
         "available.");
    writer.println("   */");
    writer.println("  public ReadOnlyEntry getLDAPEntry()");
    writer.println("  {");
    writer.println("    return ldapEntry;");
    writer.println("  }");


    // Add a method for getting the DN of the associated entry.
    writer.println();
    writer.println();
    writer.println();
    writer.println("  /**");
    writer.println("   * Retrieves the DN of the entry with which this " +
         "object is associated, if it");
    writer.println("   * is available.  It will only be available if this " +
         "object was decoded from or");
    writer.println("   * encoded to an LDAP entry.");
    writer.println("   *");
    writer.println("   * @return  The DN of the entry with which this object " +
         "is associated, or");
    writer.println("   *          {@code null} if it is not available.");
    writer.println("   */");
    writer.println("  public String getLDAPEntryDN()");
    writer.println("  {");
    writer.println("    if (ldapEntry == null)");
    writer.println("    {");
    writer.println("      return null;");
    writer.println("    }");
    writer.println("    else");
    writer.println("    {");
    writer.println("      return ldapEntry.getDN();");
    writer.println("    }");
    writer.println("  }");


    // Add getter, setter, and filter generation methods for all of the fields
    // associated with LDAP attributes.  First the fields for the RDN
    // attributes, then for the rest of the required attributes, and then for
    // the optional attributes.
    for (final String lowerName : rdnAttrs)
    {
      final AttributeTypeDefinition d = requiredAttrs.get(lowerName);
      writeFieldMethods(writer, d, types.get(lowerName), true);
    }

    for (final String lowerName : requiredAttrs.keySet())
    {
      if (rdnAttrs.contains(lowerName))
      {
        continue;
      }

      final AttributeTypeDefinition d = requiredAttrs.get(lowerName);
      writeFieldMethods(writer, d, types.get(lowerName), true);
    }

    for (final String lowerName : optionalAttrs.keySet())
    {
      final AttributeTypeDefinition d = optionalAttrs.get(lowerName);
      writeFieldMethods(writer, d, types.get(lowerName), true);
    }

    for (final String lowerName : operationalAttrs.keySet())
    {
      final AttributeTypeDefinition d = operationalAttrs.get(lowerName);
      writeFieldMethods(writer, d, types.get(lowerName), false);
    }

    writeToString(writer, className, requiredAttrs.values(),
         optionalAttrs.values(), operationalAttrs.values());

    writer.println("}");
    writer.println();
    writer.close();

    return ResultCode.SUCCESS;
  }



  /**
   * Performs an appropriate set of processing for the provided object class to
   * ensure that all of the required and optional attributes are classified
   * properly.
   *
   * @param  oc   The object class to process.
   * @param  s    The server schema.
   * @param  ra   The set of required attributes identified so far.
   * @param  rac  The object classes referenced by the required attributes.
   * @param  oa   The set of optional attributes identified so far.
   * @param  oac  The object classes referenced by the optional attributes.
   * @param  t    A map of attribute type names to Java types.
   */
  private void processObjectClass(final ObjectClassDefinition oc,
                   final Schema s,
                   final TreeMap ra,
                   final TreeMap> rac,
                   final TreeMap oa,
                   final TreeMap> oac,
                   final TreeMap t)
  {
    for (final AttributeTypeDefinition d : oc.getRequiredAttributes(s, true))
    {
      if (d.hasNameOrOID("objectClass"))
      {
        continue;
      }

      final String lowerName = StaticUtils.toLowerCase(d.getNameOrOID());
      if (ra.containsKey(lowerName))
      {
        rac.get(lowerName).add(oc.getNameOrOID());
      }
      else if (oa.containsKey(lowerName))
      {
        oa.remove(lowerName);
        ra.put(lowerName, d);

        final TreeSet ocSet = oac.remove(lowerName);
        ocSet.add(oc.getNameOrOID());
        rac.put(lowerName, ocSet);
      }
      else
      {
        final TreeSet ocSet = new TreeSet<>();
        ocSet.add(oc.getNameOrOID());
        ra.put(lowerName, d);
        rac.put(lowerName, ocSet);
        t.put(lowerName, getJavaType(s, d));
      }
    }

    for (final AttributeTypeDefinition d : oc.getOptionalAttributes(s, true))
    {
      if (d.hasNameOrOID("objectClass"))
      {
        continue;
      }

      final String lowerName = StaticUtils.toLowerCase(d.getNameOrOID());
      if (ra.containsKey(lowerName))
      {
        rac.get(lowerName).add(oc.getNameOrOID());
      }
      else if (oa.containsKey(lowerName))
      {
        oac.get(lowerName).add(oc.getNameOrOID());
      }
      else
      {
        final TreeSet ocSet = new TreeSet<>();
        ocSet.add(oc.getNameOrOID());
        oa.put(lowerName, d);
        oac.put(lowerName, ocSet);
        t.put(lowerName, getJavaType(s, d));
      }
    }
  }



  /**
   * Writes information about a field to the Java class file.
   *
   * @param  writer    The writer to which the field information should be
   *                   written.
   * @param  d         The attribute type definition.
   * @param  type      The name of the Java type to use for the field.
   * @param  ocNames   The names of the object classes for the attribute type.
   * @param  inRDN     Indicates whether the attribute should be included in
   *                   generated entry RDNs.
   * @param  required  Indicates whether the attribute should be considered
   *                   required.
   * @param  sc        The name of the structural object class for the object.
   * @param  lazy      Indicates whether the field should be marked for lazy
   *                   loading.
   * @param  terse     Indicates whether to use terse mode.
   */
  private static void writeField(final PrintWriter writer,
                           final AttributeTypeDefinition d, final String type,
                           final TreeSet ocNames,
                           final boolean inRDN, final boolean required,
                           final String sc, final boolean lazy,
                           final boolean terse)
  {
    final String attrName  = d.getNameOrOID();
    final String fieldName = PersistUtils.toJavaIdentifier(attrName);

    writer.println();

    if (inRDN)
    {
      writer.println("  // The field used for RDN attribute " + attrName + '.');
    }
    else if (required)
    {
      writer.println("  // The field used for required attribute " + attrName +
           '.');
    }
    else if (d.isOperational())
    {
      writer.println("  // The field used for operational attribute " +
           attrName + '.');
    }
    else
    {
      writer.println("  // The field used for optional attribute " + attrName +
           '.');
    }

    boolean added = false;
    if (terse && attrName.equalsIgnoreCase(fieldName))
    {
      writer.print("  @LDAPField(");
    }
    else
    {
      writer.print("  @LDAPField(attribute=\"" + attrName + '"');
      added = true;
    }

    if (ocNames.isEmpty())
    {
      // Don't need to do anything.  This should only be the case for
      // operational attributes.
    }
    else if (ocNames.size() == 1)
    {
      if ((! terse) || (! ocNames.iterator().next().equalsIgnoreCase(sc)))
      {
        if (added)
        {
          writer.println(",");
          writer.print("             objectClass=\"" +
               ocNames.iterator().next() + '"');
        }
        else
        {
          writer.println("objectClass=\"" +
               ocNames.iterator().next() + '"');
          added = true;
        }
      }
    }
    else
    {
      final Iterator iterator = ocNames.iterator();
      if (added)
      {
        writer.println(",");
        writer.println("             objectClass={ \"" +
             iterator.next() + "\",");
      }
      else
      {
        writer.println("objectClass={ \"" +
             iterator.next() + "\",");
        added = true;
      }

      while (iterator.hasNext())
      {
        final String name = iterator.next();
        if (iterator.hasNext())
        {
          writer.println("                           \"" + name + "\",");
        }
        else
        {
          writer.print("                           \"" + name + "\" }");
        }
      }
    }

    if (inRDN)
    {
      if (added)
      {
        writer.println(",");
        writer.println("             inRDN=true,");
      }
      else
      {
        writer.println("inRDN=true,");
        added = true;
      }
      writer.print("             filterUsage=FilterUsage.ALWAYS_ALLOWED");
    }
    else
    {
      if (! terse)
      {
        if (added)
        {
          writer.println(",");
          writer.print("             " +
               "filterUsage=FilterUsage.CONDITIONALLY_ALLOWED");
        }
        else
        {
          writer.print("filterUsage=FilterUsage.CONDITIONALLY_ALLOWED");
          added = true;
        }
      }
    }

    if (required)
    {
      if (added)
      {
        writer.println(",");
        writer.print("             requiredForEncode=true");
      }
      else
      {
        writer.print("requiredForEncode=true");
        added = true;
      }
    }

    if (d.isOperational())
    {
      if (added)
      {
        writer.println(",");
        writer.println("             inAdd=false,");
      }
      else
      {
        writer.println("inAdd=false,");
        added = true;
      }

      writer.print("             inModify=false");
    }

    if (lazy)
    {
      if (added)
      {
        writer.println(",");
        writer.print("             lazilyLoad=true");
      }
      else
      {
        writer.print("lazilyLoad=true");
        added = true;
      }
    }

    writer.println(")");
    if (d.isSingleValued())
    {
      writer.println("  private " + type + ' ' + fieldName + ';');
    }
    else
    {
      writer.println("  private " + type + "[] " + fieldName + ';');
    }
  }



  /**
   * Writes getter, setter, and filter creation methods for the specified
   * attribute.
   *
   * @param  writer     The writer to use to write the methods.
   * @param  d          The attribute type definition to be written.
   * @param  type       The name of the Java type to use for the attribute.
   * @param  addSetter  Indicates whether to write a setter method.
   */
  private static void writeFieldMethods(final PrintWriter writer,
                                        final AttributeTypeDefinition d,
                                        final String type,
                                        final boolean addSetter)
  {
    writer.println();
    writer.println();
    writer.println();

    final String attrName  = d.getNameOrOID();
    final String fieldName = PersistUtils.toJavaIdentifier(attrName);
    final String capFieldName = StaticUtils.capitalize(fieldName);

    if (d.isSingleValued())
    {
      if (type.equals("DN"))
      {
        writer.println("  /**");
        writer.println("   * Retrieves the first value for the field " +
             "associated with the");
        writer.println("   * " + attrName + " attribute as a DN, if present.");
        writer.println("   *");
        writer.println("   * @return  The first value for the field " +
             "associated with the");
        writer.println("   *          " + attrName + " attribute, or");
        writer.println("   *          {@code null} if the field does not " +
             "have a value.");
        writer.println("   */");
        writer.println("  public DN get" + capFieldName + "DN()");
        writer.println("  {");
        writer.println("    return " + fieldName + ';');
        writer.println("  }");

        writer.println();
        writer.println();
        writer.println();

        writer.println("  /**");
        writer.println("   * Retrieves the object referenced by the DN held " +
             "in the");
        writer.println("   * " + attrName + " attribute, if present.");
        writer.println("   *");
        writer.println("   * @param    The type of object to return.");
        writer.println("   *");
        writer.println("   * @param  connection  The connection to use to " +
             "retrieve the entry.  It must");
        writer.println("   *                     not be {@code null}.");
        writer.println("   * @param  type        The type of object as which " +
             "to decode the entry.  It");
        writer.println("   *                     must not be {@code null}, " +
             "and the class must be marked");
        writer.println("   *                     with the {@code LDAPObject} " +
             "annotation type.");
        writer.println("   *");
        writer.println("   * @return  The object decoded from the entry with " +
             "the associated DN, or");
        writer.println("   *          {@code null} if the field does not " +
             "have a value or the referenced");
        writer.println("   *          entry does not exist.");
        writer.println("   *");
        writer.println("   * @throws  LDAPException  If a problem occurs " +
             "while attempting to retrieve");
        writer.println("   *                         the entry or decode it " +
             "as an object of the");
        writer.println("   *                         specified type.");
        writer.println("   */");
        writer.println("  public  T get" + capFieldName + "Object(");
        writer.println("                    final LDAPInterface connection,");
        writer.println("                    final Class type)");
        writer.println("         throws LDAPException");
        writer.println("  {");
        writer.println("    return PersistUtils.getEntryAsObject(" + fieldName +
             ',');
        writer.println("         type, connection);");
        writer.println("  }");

        if (addSetter)
        {
          writer.println();
          writer.println();
          writer.println();

          writer.println("  /**");
          writer.println("   * Sets the value for the field associated with " +
               "the");
          writer.println("   * " + attrName + " attribute.");
          writer.println("   *");
          writer.println("   * @param  v  The value for the field associated " +
               "with the");
          writer.println("   *            " + attrName + " attribute.");
          writer.println("   */");
          writer.println("  public void set" + capFieldName + "(final DN v)");
          writer.println("  {");
          writer.println("    this." + fieldName + " = v;");
          writer.println("  }");

          writer.println();
          writer.println();
          writer.println();

          writer.println("  /**");
          writer.println("   * Sets the value for the field associated with " +
               "the");
          writer.println("   * " + attrName + " attribute.");
          writer.println("   *");
          writer.println("   * @param  v  The string representation of the " +
               "value for the field associated");
          writer.println("   *            with the " + attrName +
               " attribute.");
          writer.println("   *");
          writer.println("   * @throws  LDAPException  If the provided " +
               "string cannot be parsed as a DN.");
          writer.println("   */");
          writer.println("  public void set" + capFieldName +
               "(final String v)");
          writer.println("         throws LDAPException");
          writer.println("  {");
          writer.println("    if (v == null)");
          writer.println("    {");
          writer.println("      this." + fieldName + " = null;");
          writer.println("    }");
          writer.println("    else");
          writer.println("    {");
          writer.println("      this." + fieldName + " = new DN(v);");
          writer.println("    }");
          writer.println("  }");
        }
      }
      else
      {
        writer.println("  /**");
        writer.println("   * Retrieves the value for the field associated " +
             "with the");
        writer.println("   * " + attrName + " attribute, if present.");
        writer.println("   *");
        writer.println("   * @return  The value for the field associated " +
             "with the");
        writer.println("   *          " + attrName + " attribute, or");
        writer.println("   *          {@code null} if the field does not " +
             "have a value.");
        writer.println("   */");
        writer.println("  public " + type + " get" + capFieldName + "()");
        writer.println("  {");
        writer.println("    return " + fieldName + ';');
        writer.println("  }");

        if (addSetter)
        {
          writer.println();
          writer.println();
          writer.println();

          writer.println("  /**");
          writer.println("   * Sets the value for the field associated with " +
               "the");
          writer.println("   * " + attrName + " attribute.");
          writer.println("   *");
          writer.println("   * @param  v  The value for the field associated " +
               "with the");
          writer.println("   *            " + attrName + " attribute.");
          writer.println("   */");
          writer.println("  public void set" + capFieldName + "(final " + type +
               " v)");
          writer.println("  {");
          writer.println("    this." + fieldName + " = v;");
          writer.println("  }");
        }
      }
    }
    else
    {
      if (type.equals("DN"))
      {
        writer.println("  /**");
        writer.println("   * Retrieves the first value for the field " +
             "associated with the");
        writer.println("   * " + attrName + " attribute as a DN, if present.");
        writer.println("   *");
        writer.println("   * @return  The first value for the field " +
             "associated with the");
        writer.println("   *          " + attrName + " attribute, or");
        writer.println("   *          {@code null} if that attribute was not " +
             "present in the entry or");
        writer.println("   *          does not have any values.");
        writer.println("   */");
        writer.println("  public DN getFirst" + capFieldName + "DN()");
        writer.println("  {");
        writer.println("    if ((" + fieldName + " == null) ||");
        writer.println("        (" + fieldName + ".length == 0))");
        writer.println("    {");
        writer.println("      return null;");
        writer.println("    }");
        writer.println("    else");
        writer.println("    {");
        writer.println("      return " + fieldName + "[0];");
        writer.println("    }");
        writer.println("  }");

        writer.println();
        writer.println();
        writer.println();

        writer.println("  /**");
        writer.println("   * Retrieves the values for the field associated " +
             "with the");
        writer.println("   * " + attrName + " attribute as DNs, if present.");
        writer.println("   *");
        writer.println("   * @return  The values for the field associated " +
             "with the");
        writer.println("   *          " + attrName + " attribute, or");
        writer.println("   *          {@code null} if that attribute was not " +
             "present in the entry.");
        writer.println("   */");
        writer.println("  public DN[] get" + capFieldName + "DNs()");
        writer.println("  {");
        writer.println("    return " + fieldName + ';');
        writer.println("  }");

        writer.println();
        writer.println();
        writer.println();

        writer.println("  /**");
        writer.println("   * Retrieves the values for the field associated " +
             "with the");
        writer.println("   * " + attrName + " attribute as objects of the " +
             "specified type,");
        writer.println("   * if present.");
        writer.println("   *");
        writer.println("   * @param    The type of object to return.");
        writer.println("   *");
        writer.println("   * @param  connection  The connection to use to " +
             "retrieve the entries.  It");
        writer.println("   *                     must not be {@code null}.");
        writer.println("   * @param  type        The type of object as which " +
             "the entries should be");
        writer.println("   *                     decoded.  It must not be " +
             "{@code null}, and the class");
        writer.println("   *                     must be marked with the " +
             "{@code LDAPObject} annotation");
        writer.println("   *                     type.");
        writer.println("   *");
        writer.println("   * @return  A {@code PersistedObjects} object that " +
             "may be used to iterate");
        writer.println("   *          across the resulting objects.");
        writer.println("   *");
        writer.println("   * @throws  LDAPException  If the requested type " +
             "cannot be used with the LDAP");
        writer.println("   *                         SDK persistence " +
             "framework.");
        writer.println("   */");
        writer.println("  public  PersistedObjects get" + capFieldName +
             "Objects(");
        writer.println("                                      final " +
             "LDAPInterface connection,");
        writer.println("                                      final Class " +
             "type)");
        writer.println("         throws LDAPException");
        writer.println("  {");
        writer.println("    return PersistUtils.getEntriesAsObjects(" +
             fieldName + ',');
        writer.println("         type, connection);");
        writer.println("  }");

        if (addSetter)
        {
          writer.println();
          writer.println();
          writer.println();

          writer.println("  /**");
          writer.println("   * Sets the values for the field associated with " +
               "the");
          writer.println("   * " + attrName + " attribute.");
          writer.println("   *");
          writer.println("   * @param  v  The values for the field " +
               "associated with the");
          writer.println("   *            " + attrName + " attribute.");
          writer.println("   */");
          writer.println("  public void set" + capFieldName +
               "(final DN... v)");
          writer.println("  {");
          writer.println("    this." + fieldName + " = v;");
          writer.println("  }");

          writer.println();
          writer.println();
          writer.println();

          writer.println("  /**");
          writer.println("   * Sets the values for the field associated with " +
               "the");
          writer.println("   * " + attrName + " attribute.");
          writer.println("   *");
          writer.println("   * @param  v  The string representations of the " +
               "values for the field");
          writer.println("   *            associated with the " + attrName +
               " attribute.");
          writer.println("   *");
          writer.println("   * @throws  LDAPException  If any of the " +
               "provided strings cannot be parsed as");
          writer.println("   *                         a DN.");
          writer.println("   */");
          writer.println("  public void set" + capFieldName +
               "(final String... v)");
          writer.println("         throws LDAPException");
          writer.println("  {");
          writer.println("    if (v == null)");
          writer.println("    {");
          writer.println("      this." + fieldName + " = null;");
          writer.println("    }");
          writer.println("    else");
          writer.println("    {");
          writer.println("      this." + fieldName + " = new DN[v.length];");
          writer.println("      for (int i=0; i < v.length; i++)");
          writer.println("      {");
          writer.println("        this." + fieldName + "[i] = new DN(v[i]);");
          writer.println("      }");
          writer.println("    }");
          writer.println("  }");
        }
      }
      else
      {
        writer.println("  /**");
        writer.println("   * Retrieves the first value for the field " +
             "associated with the");
        writer.println("   * " + attrName + " attribute, if present.");
        writer.println("   *");
        writer.println("   * @return  The first value for the field " +
             "associated with the");
        writer.println("   *          " + attrName + " attribute, or");
        writer.println("   *          {@code null} if that attribute was not " +
             "present in the entry or");
        writer.println("   *          does not have any values.");
        writer.println("   */");
        writer.println("  public " + type + " getFirst" + capFieldName + "()");
        writer.println("  {");
        writer.println("    if ((" + fieldName + " == null) ||");
        writer.println("        (" + fieldName + ".length == 0))");
        writer.println("    {");
        writer.println("      return null;");
        writer.println("    }");
        writer.println("    else");
        writer.println("    {");
        writer.println("      return " + fieldName + "[0];");
        writer.println("    }");
        writer.println("  }");

        writer.println();
        writer.println();
        writer.println();

        writer.println("  /**");
        writer.println("   * Retrieves the values for the field associated " +
             "with the");
        writer.println("   * " + attrName + " attribute, if present.");
        writer.println("   *");
        writer.println("   * @return  The values for the field associated " +
             "with the");
        writer.println("   *          " + attrName + " attribute, or");
        writer.println("   *          {@code null} if that attribute was not " +
             "present in the entry.");
        writer.println("   */");
        writer.println("  public " + type + "[] get" + capFieldName + "()");
        writer.println("  {");
        writer.println("    return " + fieldName + ';');
        writer.println("  }");

        if (addSetter)
        {
          writer.println();
          writer.println();
          writer.println();

          writer.println("  /**");
          writer.println("   * Sets the values for the field associated with " +
               "the");
          writer.println("   * " + attrName + " attribute.");
          writer.println("   *");
          writer.println("   * @param  v  The values for the field " +
               "associated with the");
          writer.println("   *            " + attrName + " attribute.");
          writer.println("   */");
          writer.println("  public void set" + capFieldName + "(final " + type +
               "... v)");
          writer.println("  {");
          writer.println("    this." + fieldName + " = v;");
          writer.println("  }");
        }
      }
    }


    writer.println();
    writer.println();
    writer.println();

    writer.println("  /**");
    writer.println("   * Generates a filter that may be used to search for " +
         "objects of this type");
    writer.println("   * using the " + attrName + " attribute.");
    writer.println("   * The resulting filter may be combined with other " +
         "filter elements to create a");
    writer.println("   * more complex filter.");
    writer.println("   *");
    writer.println("   * @param  filterType  The type of filter to generate.");
    writer.println("   * @param  value       The value to use to use for the " +
         "filter.  It may be");
    writer.println("   *                     {@code null} only for a filter " +
         "type of");
    writer.println("   *                     {@code PRESENCE}.");
    writer.println("   *");
    writer.println("   * @return  The generated search filter.");
    writer.println("   *");
    writer.println("   * @throws  LDAPPersistException  If a problem is " +
         "encountered while attempting");
    writer.println("   *                                to generate the " +
         "filter.");
    writer.println("   */");
    writer.println("  public static Filter generate" + capFieldName +
         "Filter(");
    writer.println("                            final PersistFilterType " +
         "filterType,");
    writer.println("                            final " + type + " value)");
    writer.println("         throws LDAPPersistException");
    writer.println("  {");
    writer.println("    final byte[] valueBytes;");
    writer.println("    if (filterType == PersistFilterType.PRESENCE)");
    writer.println("    {");
    writer.println("      valueBytes = null;");
    writer.println("    }");
    writer.println("    else");
    writer.println("    {");
    writer.println("      if (value == null)");
    writer.println("      {");
    writer.println("        throw new LDAPPersistException(\"Unable to " +
         "generate a filter of type \" +");
    writer.println("             filterType.name() + \" with a null value " +
         "for attribute \" +");
    writer.println("             \"" + attrName + "\");");
    writer.println("      }");
    writer.println();
    writer.println("      final LDAPObjectHandler objectHandler =");
    writer.println("           getPersister().getObjectHandler();");
    writer.println("      final FieldInfo fieldInfo = " +
         "objectHandler.getFields().get(");
    writer.println("           \"" + StaticUtils.toLowerCase(attrName) +
         "\");");
    writer.println();
    writer.println("      final DefaultObjectEncoder objectEncoder = new " +
         "DefaultObjectEncoder();");
    writer.println("      valueBytes = " +
         "objectEncoder.encodeFieldValue(fieldInfo.getField(),");

    if (d.isSingleValued())
    {
      writer.println("           value,");
    }
    else
    {
      writer.println("           new " + type + "[] { value },");
    }

    writer.println("           \"" + attrName + "\").getValueByteArray();");
    writer.println("    }");
    writer.println();
    writer.println("    switch (filterType)");
    writer.println("    {");
    writer.println("      case PRESENCE:");
    writer.println("        return Filter.createPresenceFilter(");
    writer.println("             \"" + attrName + "\");");
    writer.println("      case EQUALITY:");
    writer.println("        return Filter.createEqualityFilter(");
    writer.println("             \"" + attrName + "\",");
    writer.println("             valueBytes);");
    writer.println("      case STARTS_WITH:");
    writer.println("        return Filter.createSubstringFilter(");
    writer.println("             \"" + attrName + "\",");
    writer.println("             valueBytes, null, null);");
    writer.println("      case ENDS_WITH:");
    writer.println("        return Filter.createSubstringFilter(");
    writer.println("             \"" + attrName + "\",");
    writer.println("             null, null, valueBytes);");
    writer.println("      case CONTAINS:");
    writer.println("        return Filter.createSubstringFilter(");
    writer.println("             \"" + attrName + "\",");
    writer.println("             null, new byte[][] { valueBytes }, null);");
    writer.println("      case GREATER_OR_EQUAL:");
    writer.println("        return Filter.createGreaterOrEqualFilter(");
    writer.println("             \"" + attrName + "\",");
    writer.println("             valueBytes);");
    writer.println("      case LESS_OR_EQUAL:");
    writer.println("        return Filter.createLessOrEqualFilter(");
    writer.println("             \"" + attrName + "\",");
    writer.println("             valueBytes);");
    writer.println("      case APPROXIMATELY_EQUAL_TO:");
    writer.println("        return Filter.createApproximateMatchFilter(");
    writer.println("             \"" + attrName + "\",");
    writer.println("             valueBytes);");
    writer.println("      default:");
    writer.println("        // This should never happen.");
    writer.println("        throw new LDAPPersistException(\"Unrecognized " +
         "filter type \" +");
    writer.println("             filterType.name());");
    writer.println("    }");
    writer.println("  }");
  }



  /**
   * Writes a {@code toString} method for the generated class.
   *
   * @param  writer            The writer to use to write the methods.
   * @param  className         The base name (without package information) for
   *                           the generated class.
   * @param  requiredAttrs     The set of required attributes for the generated
   *                           class.
   * @param  optionalAttrs     The set of optional attributes for the generated
   *                           class.
   * @param  operationalAttrs  The set of operational attributes for the
   *                           generated class.
   */
  private static void writeToString(final PrintWriter writer,
               final String className,
               final Collection requiredAttrs,
               final Collection optionalAttrs,
               final Collection operationalAttrs)
  {
    writer.println();
    writer.println();
    writer.println();
    writer.println("  /**");
    writer.println("   * Retrieves a string representation of this");
    writer.println("   * {@code " + className + "} object.");
    writer.println("   *");
    writer.println("   * @return  A string representation of this");
    writer.println("   *          {@code " + className + "} object.");
    writer.println("   */");
    writer.println("  @Override()");
    writer.println("  public String toString()");
    writer.println("  {");
    writer.println("    final StringBuilder buffer = new StringBuilder();");
    writer.println("    toString(buffer);");
    writer.println("    return buffer.toString();");
    writer.println("  }");

    writer.println();
    writer.println();
    writer.println();
    writer.println("  /**");
    writer.println("   * Appends a string representation of this");
    writer.println("   * {@code " + className + "} object");
    writer.println("   * to the provided buffer.");
    writer.println("   *");
    writer.println("   * @param  buffer  The buffer to which the string " +
         "representation should be");
    writer.println("   *                 appended.");
    writer.println("   */");
    writer.println("  public void toString(final StringBuilder buffer)");
    writer.println("  {");
    writer.println("    buffer.append(\"" + className + "(\");");
    writer.println();
    writer.println("    boolean appended = false;");
    writer.println("    if (ldapEntry != null)");
    writer.println("    {");
    writer.println("      appended = true;");
    writer.println("      buffer.append(\"entryDN='\");");
    writer.println("      buffer.append(ldapEntry.getDN());");
    writer.println("      buffer.append('\\'');");
    writer.println("    }");

    for (final AttributeTypeDefinition d : requiredAttrs)
    {
      writeToStringField(writer, d);
    }

    for (final AttributeTypeDefinition d : optionalAttrs)
    {
      writeToStringField(writer, d);
    }

    for (final AttributeTypeDefinition d : operationalAttrs)
    {
      writeToStringField(writer, d);
    }

    writer.println();
    writer.println("    buffer.append(')');");
    writer.println("  }");
  }



  /**
   * Writes information about the provided field for use in the {@code toString}
   * method.
   *
   * @param  w  The writer to use to write the {@code toString} content.
   * @param  d  The attribute type definition for the field to write.
   */
  private static void writeToStringField(final PrintWriter w,
                                         final AttributeTypeDefinition d)
  {
    final String fieldName = PersistUtils.toJavaIdentifier(d.getNameOrOID());
    w.println();
    w.println("    if (" +  fieldName + " != null)");
    w.println("    {");
    w.println("      if (appended)");
    w.println("      {");
    w.println("        buffer.append(\", \");");
    w.println("      }");
    w.println("      appended = true;");
    w.println("      buffer.append(\"" + fieldName + "=\");");
    if (d.isSingleValued())
    {
      w.println("      buffer.append(" + fieldName + ");");
    }
    else
    {
      w.println("      buffer.append(Arrays.toString(" + fieldName + "));");
    }
    w.println("    }");
  }



  /**
   * Retrieves the Java type to use for the provided attribute type definition.
   * For multi-valued attributes, the value returned will be the base type
   * without square brackets to indicate an array.
   *
   * @param  schema  The schema to use to determine the syntax for the
   *                 attribute.
   * @param  d       The attribute type definition for which to get the Java
   *                 type.
   *
   * @return  The Java type to use for the provided attribute type definition.
   */
  private String getJavaType(final Schema schema,
                             final AttributeTypeDefinition d)
  {
    if (! d.isSingleValued())
    {
      needArrays = true;
    }

    final String syntaxOID = d.getSyntaxOID(schema);
    if (syntaxOID == null)
    {
      return "String";
    }

    final String oid;
    final int bracePos = syntaxOID.indexOf('{');
    if (bracePos > 0)
    {
      oid = syntaxOID.substring(0, bracePos);
    }
    else
    {
      oid = syntaxOID;
    }

    if (oid.equals("1.3.6.1.4.1.1466.115.121.1.7"))
    {
      // Boolean
      return "Boolean";
    }
    else if (oid.equals("1.3.6.1.4.1.4203.1.1.2") ||
             oid.equals("1.3.6.1.4.1.1466.115.121.1.5") ||
             oid.equals("1.3.6.1.4.1.1466.115.121.1.8") ||
             oid.equals("1.3.6.1.4.1.1466.115.121.1.9") ||
             oid.equals("1.3.6.1.4.1.1466.115.121.1.10") ||
             oid.equals("1.3.6.1.4.1.1466.115.121.1.28") ||
             oid.equals("1.3.6.1.4.1.1466.115.121.1.40"))
    {
      // auth password
      // binary
      // certificate
      // certificate list
      // certificate pair
      // JPEG
      // octet string
      return "byte[]";
    }
    else if (oid.equals("1.3.6.1.4.1.1466.115.121.1.24"))
    {
      // generalized time.
      needDate = true;
      return "Date";
    }
    else if (oid.equals("1.3.6.1.4.1.1466.115.121.1.27"))
    {
      // integer
      return "Long";
    }
    else if (oid.equals("1.3.6.1.4.1.1466.115.121.1.12") ||
             oid.equals("1.3.6.1.4.1.1466.115.121.1.34"))
    {
      // DN
      // name and optional UID
      needDN = true;
      if (! d.isSingleValued())
      {
        needPersistedObjects = true;
      }
      return "DN";
    }
    else
    {
      return "String";
    }
  }



  /**
   * {@inheritDoc}
   */
  @Override()
  public LinkedHashMap getExampleUsages()
  {
    final LinkedHashMap examples =
         new LinkedHashMap<>(StaticUtils.computeMapCapacity(1));

    final String[] args =
    {
      "--hostname", "server.example.com",
      "--port", "389",
      "--bindDN", "uid=admin,dc=example,dc=com",
      "--bindPassword", "password",
      "--outputDirectory", "src/com/example",
      "--structuralClass", "myStructuralClass",
      "--auxiliaryClass", "auxClass1",
      "--auxiliaryClass", "auxClass2",
      "--rdnAttribute", "cn",
      "--defaultParentDN", "dc=example,dc=com",
      "--packageName", "com.example",
      "--className", "MyObject"
    };
    examples.put(args, INFO_GEN_SOURCE_EXAMPLE_1.get());

    return examples;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy