001/*
002 * Copyright 2009-2016 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2009-2016 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.ldap.sdk.persist;
022
023
024
025import java.io.File;
026import java.io.FileWriter;
027import java.io.OutputStream;
028import java.io.PrintWriter;
029import java.io.Serializable;
030import java.util.Arrays;
031import java.util.Collection;
032import java.util.Date;
033import java.util.Iterator;
034import java.util.LinkedHashMap;
035import java.util.TreeMap;
036import java.util.TreeSet;
037
038import com.unboundid.ldap.sdk.DN;
039import com.unboundid.ldap.sdk.Entry;
040import com.unboundid.ldap.sdk.Filter;
041import com.unboundid.ldap.sdk.LDAPConnection;
042import com.unboundid.ldap.sdk.LDAPException;
043import com.unboundid.ldap.sdk.LDAPInterface;
044import com.unboundid.ldap.sdk.ReadOnlyEntry;
045import com.unboundid.ldap.sdk.ResultCode;
046import com.unboundid.ldap.sdk.Version;
047import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
048import com.unboundid.ldap.sdk.schema.ObjectClassDefinition;
049import com.unboundid.ldap.sdk.schema.ObjectClassType;
050import com.unboundid.ldap.sdk.schema.Schema;
051import com.unboundid.util.LDAPCommandLineTool;
052import com.unboundid.util.Mutable;
053import com.unboundid.util.ThreadSafety;
054import com.unboundid.util.ThreadSafetyLevel;
055import com.unboundid.util.args.ArgumentException;
056import com.unboundid.util.args.ArgumentParser;
057import com.unboundid.util.args.BooleanArgument;
058import com.unboundid.util.args.DNArgument;
059import com.unboundid.util.args.FileArgument;
060import com.unboundid.util.args.StringArgument;
061
062import static com.unboundid.ldap.sdk.persist.PersistMessages.*;
063import static com.unboundid.util.Debug.*;
064import static com.unboundid.util.StaticUtils.*;
065
066
067
068/**
069 * This class provides a tool which can be used to generate source code for a
070 * Java class file based on information read from the schema of an LDAP
071 * directory server.
072 */
073@Mutable()
074@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
075public final class GenerateSourceFromSchema
076       extends LDAPCommandLineTool
077       implements Serializable
078{
079  /**
080   * The serial version UID for this serializable class.
081   */
082  private static final long serialVersionUID = 3488976364950590266L;
083
084
085
086  /**
087   * A pre-allocated empty tree set.
088   */
089  private static final TreeSet<String> EMPTY_TREE_SET = new TreeSet<String>();
090
091
092
093  // Arguments used by this tool.
094  private BooleanArgument terseArg;
095  private DNArgument      defaultParentDNArg;
096  private FileArgument    outputDirectoryArg;
097  private StringArgument  auxiliaryClassArg;
098  private StringArgument  classNameArg;
099  private StringArgument  lazyAttributeArg;
100  private StringArgument  operationalAttributeArg;
101  private StringArgument  packageNameArg;
102  private StringArgument  rdnAttributeArg;
103  private StringArgument  structuralClassArg;
104
105  // Indicates whether any multivalued attributes have been identified, and
106  // therefore we need to include java.util.Arrays in the import list.
107  private boolean needArrays;
108
109  // Indicates whether any date attributes have been identified, and therefore
110  // we need to include java.util.Date in the import list.
111  private boolean needDate;
112
113  // Indicates whether any DN-syntax attributes have been identified, and
114  // therefore we need to include com.unboundid.ldap.sdk.DN in the import list.
115  private boolean needDN;
116
117  // Indicates whether
118  // Indicates whether any DN-syntax attributes have been identified, and
119  // therefore we need to include
120  // com.unboundid.ldap.sdk.persist.PersistedObjects in the import list.
121  private boolean needPersistedObjects;
122
123
124
125  /**
126   * Parse the provided command line arguments and perform the appropriate
127   * processing.
128   *
129   * @param  args  The command line arguments provided to this program.
130   */
131  public static void main(final String[] args)
132  {
133    final ResultCode resultCode = main(args, System.out, System.err);
134    if (resultCode != ResultCode.SUCCESS)
135    {
136      System.exit(resultCode.intValue());
137    }
138  }
139
140
141
142  /**
143   * Parse the provided command line arguments and perform the appropriate
144   * processing.
145   *
146   * @param  args       The command line arguments provided to this program.
147   * @param  outStream  The output stream to which standard out should be
148   *                    written.  It may be {@code null} if output should be
149   *                    suppressed.
150   * @param  errStream  The output stream to which standard error should be
151   *                    written.  It may be {@code null} if error messages
152   *                    should be suppressed.
153   *
154   * @return  A result code indicating whether the processing was successful.
155   */
156  public static ResultCode main(final String[] args,
157                                final OutputStream outStream,
158                                final OutputStream errStream)
159  {
160    final GenerateSourceFromSchema tool =
161         new GenerateSourceFromSchema(outStream, errStream);
162    return tool.runTool(args);
163  }
164
165
166
167  /**
168   * Creates a new instance of this tool.
169   *
170   * @param  outStream  The output stream to which standard out should be
171   *                    written.  It may be {@code null} if output should be
172   *                    suppressed.
173   * @param  errStream  The output stream to which standard error should be
174   *                    written.  It may be {@code null} if error messages
175   *                    should be suppressed.
176   */
177  public GenerateSourceFromSchema(final OutputStream outStream,
178                                  final OutputStream errStream)
179  {
180    super(outStream, errStream);
181
182    needArrays           = false;
183    needDate             = false;
184    needDN               = false;
185    needPersistedObjects = false;
186  }
187
188
189
190  /**
191   * {@inheritDoc}
192   */
193  @Override()
194  public String getToolName()
195  {
196    return "generate-source-from-schema";
197  }
198
199
200
201  /**
202   * {@inheritDoc}
203   */
204  @Override()
205  public String getToolDescription()
206  {
207    return INFO_GEN_SOURCE_TOOL_DESCRIPTION.get();
208  }
209
210
211
212  /**
213   * Retrieves the version string for this tool.
214   *
215   * @return  The version string for this tool.
216   */
217  @Override()
218  public String getToolVersion()
219  {
220    return Version.NUMERIC_VERSION_STRING;
221  }
222
223
224
225  /**
226   * Indicates whether this tool should provide support for an interactive mode,
227   * in which the tool offers a mode in which the arguments can be provided in
228   * a text-driven menu rather than requiring them to be given on the command
229   * line.  If interactive mode is supported, it may be invoked using the
230   * "--interactive" argument.  Alternately, if interactive mode is supported
231   * and {@link #defaultsToInteractiveMode()} returns {@code true}, then
232   * interactive mode may be invoked by simply launching the tool without any
233   * arguments.
234   *
235   * @return  {@code true} if this tool supports interactive mode, or
236   *          {@code false} if not.
237   */
238  @Override()
239  public boolean supportsInteractiveMode()
240  {
241    return true;
242  }
243
244
245
246  /**
247   * Indicates whether this tool defaults to launching in interactive mode if
248   * the tool is invoked without any command-line arguments.  This will only be
249   * used if {@link #supportsInteractiveMode()} returns {@code true}.
250   *
251   * @return  {@code true} if this tool defaults to using interactive mode if
252   *          launched without any command-line arguments, or {@code false} if
253   *          not.
254   */
255  @Override()
256  public boolean defaultsToInteractiveMode()
257  {
258    return true;
259  }
260
261
262
263  /**
264   * Indicates whether this tool supports the use of a properties file for
265   * specifying default values for arguments that aren't specified on the
266   * command line.
267   *
268   * @return  {@code true} if this tool supports the use of a properties file
269   *          for specifying default values for arguments that aren't specified
270   *          on the command line, or {@code false} if not.
271   */
272  @Override()
273  public boolean supportsPropertiesFile()
274  {
275    return true;
276  }
277
278
279
280  /**
281   * Indicates whether the LDAP-specific arguments should include alternate
282   * versions of all long identifiers that consist of multiple words so that
283   * they are available in both camelCase and dash-separated versions.
284   *
285   * @return  {@code true} if this tool should provide multiple versions of
286   *          long identifiers for LDAP-specific arguments, or {@code false} if
287   *          not.
288   */
289  @Override()
290  protected boolean includeAlternateLongIdentifiers()
291  {
292    return true;
293  }
294
295
296
297  /**
298   * {@inheritDoc}
299   */
300  @Override()
301  public void addNonLDAPArguments(final ArgumentParser parser)
302         throws ArgumentException
303  {
304    outputDirectoryArg = new FileArgument('d', "outputDirectory", false, 1,
305         INFO_GEN_SOURCE_VALUE_PLACEHOLDER_PATH.get(),
306         INFO_GEN_SOURCE_ARG_DESCRIPTION_OUTPUT_DIRECTORY.get(), true, true,
307         false, true);
308    outputDirectoryArg.addLongIdentifier("output-directory");
309    parser.addArgument(outputDirectoryArg);
310
311    structuralClassArg = new StringArgument('s', "structuralClass", true, 1,
312         INFO_GEN_SOURCE_VALUE_PLACEHOLDER_NAME.get(),
313         INFO_GEN_SOURCE_ARG_DESCRIPTION_STRUCTURAL_CLASS.get());
314    structuralClassArg.addLongIdentifier("structural-class");
315    parser.addArgument(structuralClassArg);
316
317    auxiliaryClassArg = new StringArgument('a', "auxiliaryClass", false, 0,
318         INFO_GEN_SOURCE_VALUE_PLACEHOLDER_NAME.get(),
319         INFO_GEN_SOURCE_ARG_DESCRIPTION_AUXILIARY_CLASS.get());
320    auxiliaryClassArg.addLongIdentifier("auxiliary-class");
321    parser.addArgument(auxiliaryClassArg);
322
323    rdnAttributeArg = new StringArgument('r', "rdnAttribute", true, 0,
324         INFO_GEN_SOURCE_VALUE_PLACEHOLDER_NAME.get(),
325         INFO_GEN_SOURCE_ARG_DESCRIPTION_RDN_ATTRIBUTE.get());
326    rdnAttributeArg.addLongIdentifier("rdn-attribute");
327    parser.addArgument(rdnAttributeArg);
328
329    lazyAttributeArg = new StringArgument('l', "lazyAttribute", false, 0,
330         INFO_GEN_SOURCE_VALUE_PLACEHOLDER_NAME.get(),
331         INFO_GEN_SOURCE_ARG_DESCRIPTION_LAZY_ATTRIBUTE.get());
332    lazyAttributeArg.addLongIdentifier("lazy-attribute");
333    parser.addArgument(lazyAttributeArg);
334
335    operationalAttributeArg = new StringArgument('O', "operationalAttribute",
336         false, 0, INFO_GEN_SOURCE_VALUE_PLACEHOLDER_NAME.get(),
337         INFO_GEN_SOURCE_ARG_DESCRIPTION_OPERATIONAL_ATTRIBUTE.get());
338    operationalAttributeArg.addLongIdentifier("operational-attribute");
339    parser.addArgument(operationalAttributeArg);
340
341    defaultParentDNArg = new DNArgument('b', "defaultParentDN", false, 1,
342         INFO_GEN_SOURCE_VALUE_PLACEHOLDER_DN.get(),
343         INFO_GEN_SOURCE_ARG_DESCRIPTION_DEFAULT_PARENT_DN.get());
344    defaultParentDNArg.addLongIdentifier("default-parent-dn");
345    parser.addArgument(defaultParentDNArg);
346
347    packageNameArg = new StringArgument('n', "packageName", false, 1,
348         INFO_GEN_SOURCE_VALUE_PLACEHOLDER_NAME.get(),
349         INFO_GEN_SOURCE_ARG_DESCRIPTION_PACKAGE_NAME.get());
350    packageNameArg.addLongIdentifier("package-name");
351    parser.addArgument(packageNameArg);
352
353    classNameArg = new StringArgument('c', "className", false, 1,
354         INFO_GEN_SOURCE_VALUE_PLACEHOLDER_NAME.get(),
355         INFO_GEN_SOURCE_ARG_DESCRIPTION_CLASS_NAME.get());
356    classNameArg.addLongIdentifier("class-name");
357    parser.addArgument(classNameArg);
358
359    terseArg = new BooleanArgument('t', "terse", 1,
360         INFO_GEN_SOURCE_ARG_DESCRIPTION_TERSE.get());
361    parser.addArgument(terseArg);
362  }
363
364
365
366  /**
367   * {@inheritDoc}
368   */
369  @Override()
370  public ResultCode doToolProcessing()
371  {
372    // Establish a connection to the target directory server and retrieve the
373    // schema.
374    final LDAPConnection conn;
375    try
376    {
377      conn = getConnection();
378    }
379    catch (LDAPException le)
380    {
381      debugException(le);
382      err(ERR_GEN_SOURCE_CANNOT_CONNECT.get(getExceptionMessage(le)));
383      return le.getResultCode();
384    }
385
386    final Schema schema;
387    try
388    {
389      schema = conn.getSchema();
390      if (schema == null)
391      {
392        err(ERR_GEN_SOURCE_CANNOT_READ_SCHEMA.get(
393             ERR_GEN_SOURCE_SCHEMA_NOT_RETURNED.get()));
394        return ResultCode.NO_RESULTS_RETURNED;
395      }
396    }
397    catch (LDAPException le)
398    {
399      debugException(le);
400      err(ERR_GEN_SOURCE_CANNOT_READ_SCHEMA.get(getExceptionMessage(le)));
401      return le.getResultCode();
402    }
403    finally
404    {
405      conn.close();
406    }
407
408    return generateSourceFile(schema, terseArg.isPresent());
409  }
410
411
412
413  /**
414   * Generates the source file using the information in the provided schema.
415   *
416   * @param  schema  The schema to use to generate the source file.
417   * @param  terse   Indicates whether to use terse mode when generating the
418   *                 source file.  If this is {@code true}, then all optional
419   *                 elements will be omitted from annotations.
420   *
421   * @return  A result code obtained for the processing.
422   */
423  private ResultCode generateSourceFile(final Schema schema,
424                                        final boolean terse)
425  {
426    // Retrieve and process the structural object class.
427    final TreeMap<String,AttributeTypeDefinition> requiredAttrs =
428         new TreeMap<String,AttributeTypeDefinition>();
429    final TreeMap<String,AttributeTypeDefinition> optionalAttrs =
430         new TreeMap<String,AttributeTypeDefinition>();
431    final TreeMap<String,TreeSet<String>> requiredAttrOCs =
432         new TreeMap<String,TreeSet<String>>();
433    final TreeMap<String,TreeSet<String>> optionalAttrOCs =
434         new TreeMap<String,TreeSet<String>>();
435    final TreeMap<String,String> types = new TreeMap<String,String>();
436
437    final String structuralClassName = structuralClassArg.getValue();
438    final ObjectClassDefinition structuralOC =
439         schema.getObjectClass(structuralClassName);
440    if (structuralOC == null)
441    {
442      err(ERR_GEN_SOURCE_STRUCTURAL_CLASS_NOT_FOUND.get(structuralClassName));
443      return ResultCode.PARAM_ERROR;
444    }
445
446    if (structuralOC.getObjectClassType(schema) != ObjectClassType.STRUCTURAL)
447    {
448      err(ERR_GEN_SOURCE_STRUCTURAL_CLASS_NOT_STRUCTURAL.get(
449           structuralClassName));
450      return ResultCode.PARAM_ERROR;
451    }
452
453    processObjectClass(structuralOC, schema, requiredAttrs, requiredAttrOCs,
454         optionalAttrs, optionalAttrOCs, types);
455
456
457    // Retrieve and process the auxiliary object classes.
458    final TreeMap<String,ObjectClassDefinition> auxiliaryOCs =
459         new TreeMap<String,ObjectClassDefinition>();
460    if (auxiliaryClassArg.isPresent())
461    {
462      for (final String s : auxiliaryClassArg.getValues())
463      {
464        final ObjectClassDefinition oc = schema.getObjectClass(s);
465        if (oc == null)
466        {
467          err(ERR_GEN_SOURCE_AUXILIARY_CLASS_NOT_FOUND.get(s));
468          return ResultCode.PARAM_ERROR;
469        }
470
471        if  (oc.getObjectClassType(schema) != ObjectClassType.AUXILIARY)
472        {
473          err(ERR_GEN_SOURCE_AUXILIARY_CLASS_NOT_AUXILIARY.get(s));
474          return ResultCode.PARAM_ERROR;
475        }
476
477        auxiliaryOCs.put(toLowerCase(s), oc);
478
479        processObjectClass(oc, schema, requiredAttrs, requiredAttrOCs,
480             optionalAttrs, optionalAttrOCs, types);
481      }
482    }
483
484
485    // Determine the appropriate set of superior object classes.
486    final TreeMap<String,ObjectClassDefinition> superiorOCs =
487         new TreeMap<String,ObjectClassDefinition>();
488    for (final ObjectClassDefinition s :
489         structuralOC.getSuperiorClasses(schema, true))
490    {
491      superiorOCs.put(toLowerCase(s.getNameOrOID()), s);
492    }
493
494    for (final ObjectClassDefinition d : auxiliaryOCs.values())
495    {
496      for (final ObjectClassDefinition s : d.getSuperiorClasses(schema, true))
497      {
498        superiorOCs.put(toLowerCase(s.getNameOrOID()), s);
499      }
500    }
501
502    superiorOCs.remove(toLowerCase(structuralClassName));
503    for (final String s : auxiliaryOCs.keySet())
504    {
505      superiorOCs.remove(s);
506    }
507
508
509    // Retrieve and process the operational attributes.
510    final TreeMap<String,AttributeTypeDefinition> operationalAttrs =
511         new TreeMap<String,AttributeTypeDefinition>();
512    if (operationalAttributeArg.isPresent())
513    {
514      for (final String s : operationalAttributeArg.getValues())
515      {
516        final AttributeTypeDefinition d = schema.getAttributeType(s);
517        if (d == null)
518        {
519          err(ERR_GEN_SOURCE_OPERATIONAL_ATTRIBUTE_NOT_DEFINED.get(s));
520          return ResultCode.PARAM_ERROR;
521        }
522        else if (! d.isOperational())
523        {
524          err(ERR_GEN_SOURCE_OPERATIONAL_ATTRIBUTE_NOT_OPERATIONAL.get(s));
525          return ResultCode.PARAM_ERROR;
526        }
527        else
528        {
529          final String lowerName = toLowerCase(s);
530          operationalAttrs.put(lowerName, d);
531          types.put(lowerName, getJavaType(schema, d));
532        }
533      }
534    }
535
536
537    // Make sure all of the configured RDN attributes are allowed by at least
538    // one of the associated object classes.
539    final TreeSet<String> rdnAttrs = new TreeSet<String>();
540    for (final String s : rdnAttributeArg.getValues())
541    {
542      final AttributeTypeDefinition d = schema.getAttributeType(s);
543      if (d == null)
544      {
545        err(ERR_GEN_SOURCE_RDN_ATTRIBUTE_NOT_DEFINED.get(s));
546        return ResultCode.PARAM_ERROR;
547      }
548
549      final String lowerName = toLowerCase(d.getNameOrOID());
550      rdnAttrs.add(lowerName);
551      if (requiredAttrs.containsKey(lowerName))
552      {
553        // No action required.
554      }
555      else if (optionalAttrs.containsKey(lowerName))
556      {
557        // Move the attribute to the required set.
558        requiredAttrs.put(lowerName, optionalAttrs.remove(lowerName));
559        requiredAttrOCs.put(lowerName, optionalAttrOCs.remove(lowerName));
560      }
561      else
562      {
563        err(ERR_GEN_SOURCE_RDN_ATTRIBUTE_NOT_DEFINED.get(s));
564        return ResultCode.PARAM_ERROR;
565      }
566    }
567
568
569    // Make sure all of the configured lazily-loaded attributes are allowed by
570    // at least one of the associated object classes or matches a configured
571    // operational attribute.
572    final TreeSet<String> lazyAttrs = new TreeSet<String>();
573    for (final String s : lazyAttributeArg.getValues())
574    {
575      final AttributeTypeDefinition d = schema.getAttributeType(s);
576      if (d == null)
577      {
578        err(ERR_GEN_SOURCE_LAZY_ATTRIBUTE_NOT_DEFINED.get(s));
579        return ResultCode.PARAM_ERROR;
580      }
581
582      final String lowerName = toLowerCase(d.getNameOrOID());
583      lazyAttrs.add(lowerName);
584      if (requiredAttrs.containsKey(lowerName) ||
585          optionalAttrs.containsKey(lowerName) ||
586          operationalAttrs.containsKey(lowerName))
587      {
588        // No action required.
589      }
590      else
591      {
592        err(ERR_GEN_SOURCE_LAZY_ATTRIBUTE_NOT_ALLOWED.get(s));
593        return ResultCode.PARAM_ERROR;
594      }
595    }
596
597
598    final String className;
599    if (classNameArg.isPresent())
600    {
601      className = classNameArg.getValue();
602      final StringBuilder invalidReason = new StringBuilder();
603      if (! PersistUtils.isValidJavaIdentifier(className, invalidReason))
604      {
605        err(ERR_GEN_SOURCE_INVALID_CLASS_NAME.get(className,
606             invalidReason.toString()));
607        return ResultCode.PARAM_ERROR;
608      }
609    }
610    else
611    {
612      className =
613           capitalize(PersistUtils.toJavaIdentifier(structuralClassName));
614    }
615
616
617    final File sourceFile = new File(outputDirectoryArg.getValue(),
618         className + ".java");
619    final PrintWriter writer;
620    try
621    {
622      writer = new PrintWriter(new FileWriter(sourceFile));
623    }
624    catch (Exception e)
625    {
626      debugException(e);
627      err(ERR_GEN_SOURCE_CANNOT_CREATE_WRITER.get(sourceFile.getAbsolutePath(),
628           getExceptionMessage(e)));
629      return ResultCode.LOCAL_ERROR;
630    }
631
632
633    if (packageNameArg.isPresent())
634    {
635      final String packageName = packageNameArg.getValue();
636      if (packageName.length() > 0)
637      {
638        writer.println("package " + packageName + ';');
639        writer.println();
640        writer.println();
641        writer.println();
642      }
643    }
644
645    boolean javaImports = false;
646    if (needArrays)
647    {
648      writer.println("import " + Arrays.class.getName() + ';');
649      javaImports = true;
650    }
651
652    if (needDate)
653    {
654      writer.println("import " + Date.class.getName() + ';');
655      javaImports = true;
656    }
657
658    if (javaImports)
659    {
660      writer.println();
661    }
662
663    if (needDN)
664    {
665      writer.println("import " + DN.class.getName() + ';');
666    }
667
668    writer.println("import " + Entry.class.getName() + ';');
669    writer.println("import " + Filter.class.getName() + ';');
670
671    if (needDN)
672    {
673      writer.println("import " + LDAPException.class.getName() + ';');
674      writer.println("import " + LDAPInterface.class.getName() + ';');
675    }
676
677    writer.println("import " + ReadOnlyEntry.class.getName() + ';');
678    writer.println("import " + DefaultObjectEncoder.class.getName() + ';');
679    writer.println("import " + FieldInfo.class.getName() + ';');
680    writer.println("import " + FilterUsage.class.getName() + ';');
681    writer.println("import " + LDAPEntryField.class.getName() + ';');
682    writer.println("import " + LDAPField.class.getName() + ';');
683    writer.println("import " + LDAPObject.class.getName() + ';');
684    writer.println("import " + LDAPObjectHandler.class.getName() + ';');
685    writer.println("import " + LDAPPersister.class.getName() + ';');
686    writer.println("import " + LDAPPersistException.class.getName() + ';');
687
688    if (needPersistedObjects)
689    {
690      writer.println("import " + PersistedObjects.class.getName() + ';');
691    }
692
693    writer.println("import " + PersistFilterType.class.getName() + ';');
694
695    if (needDN)
696    {
697      writer.println("import " + PersistUtils.class.getName() + ';');
698    }
699
700    writer.println();
701    writer.println();
702    writer.println();
703    writer.println("/**");
704    writer.println(" * This class provides an implementation of an object " +
705         "that can be used to");
706    writer.println(" * represent " + structuralClassName +
707         " objects in the directory.");
708    writer.println(" * It was generated by the " + getToolName() +
709         " tool provided with the");
710    writer.println(" * UnboundID LDAP SDK for Java.  It " +
711         "may be customized as desired to better suit");
712    writer.println(" * your needs.");
713    writer.println(" */");
714    writer.println("@LDAPObject(structuralClass=\"" + structuralClassName +
715         "\",");
716
717    switch (auxiliaryOCs.size())
718    {
719      case 0:
720        // No action required.
721        break;
722
723      case 1:
724        writer.println("            auxiliaryClass=\"" +
725             auxiliaryOCs.values().iterator().next().getNameOrOID() + "\",");
726        break;
727
728      default:
729        final Iterator<ObjectClassDefinition> iterator =
730             auxiliaryOCs.values().iterator();
731        writer.println("            auxiliaryClass={ \"" +
732             iterator.next().getNameOrOID() + "\",");
733        while (iterator.hasNext())
734        {
735          final String ocName = iterator.next().getNameOrOID();
736          if (iterator.hasNext())
737          {
738            writer.println("                             \"" + ocName +
739                 "\",");
740          }
741          else
742          {
743            writer.println("                             \"" + ocName +
744                 "\" },");
745          }
746        }
747        break;
748    }
749
750    switch (superiorOCs.size())
751    {
752      case 0:
753        // No action required.
754        break;
755
756      case 1:
757        writer.println("            superiorClass=\"" +
758             superiorOCs.values().iterator().next().getNameOrOID() + "\",");
759        break;
760
761      default:
762        final Iterator<ObjectClassDefinition> iterator =
763             superiorOCs.values().iterator();
764        writer.println("            superiorClass={ \"" +
765             iterator.next().getNameOrOID() + "\",");
766        while (iterator.hasNext())
767        {
768          final String ocName = iterator.next().getNameOrOID();
769          if (iterator.hasNext())
770          {
771            writer.println("                             \"" + ocName +
772                 "\",");
773          }
774          else
775          {
776            writer.println("                             \"" + ocName +
777                 "\" },");
778          }
779        }
780        break;
781    }
782
783    if (defaultParentDNArg.isPresent())
784    {
785      writer.println("            defaultParentDN=\"" +
786           defaultParentDNArg.getValue() + "\",");
787    }
788
789    writer.println("            postDecodeMethod=\"doPostDecode\",");
790    writer.println("            postEncodeMethod=\"doPostEncode\")");
791    writer.println("public class " + className);
792    writer.println("{");
793
794    if (! terse)
795    {
796      writer.println("  /*");
797      writer.println("   * NOTE:  This class includes a number of annotation " +
798           "elements which are not");
799      writer.println("   * required but have been provided to make it easier " +
800           "to edit the resulting");
801      writer.println("   * source code.  If you want to exclude these " +
802           "unnecessary annotation");
803      writer.println("   * elements, use the '--terse' command-line argument.");
804      writer.println("   */");
805      writer.println();
806      writer.println();
807      writer.println();
808    }
809
810    writer.println("  // The field to use to hold a read-only copy of the " +
811         "associated entry.");
812    writer.println("  @LDAPEntryField()");
813    writer.println("  private ReadOnlyEntry ldapEntry;");
814
815
816    // Add all of the fields.  First the fields for the RDN attributes, then
817    // for the rest of the required attributes, then for the optional
818    // attributes, and finally any operational attributes.
819    for (final String lowerName : rdnAttrs)
820    {
821      final AttributeTypeDefinition d = requiredAttrs.get(lowerName);
822      final TreeSet<String> ocNames = requiredAttrOCs.get(lowerName);
823      writeField(writer, d, types.get(lowerName), ocNames, true, true,
824           structuralClassName, false, terse);
825    }
826
827    for (final String lowerName : requiredAttrs.keySet())
828    {
829      if (rdnAttrs.contains(lowerName))
830      {
831        continue;
832      }
833
834      final AttributeTypeDefinition d = requiredAttrs.get(lowerName);
835      final TreeSet<String> ocNames = requiredAttrOCs.get(lowerName);
836      writeField(writer, d, types.get(lowerName), ocNames, false, true,
837           structuralClassName, lazyAttrs.contains(lowerName), terse);
838    }
839
840    for (final String lowerName : optionalAttrs.keySet())
841    {
842      final AttributeTypeDefinition d = optionalAttrs.get(lowerName);
843      final TreeSet<String> ocNames = optionalAttrOCs.get(lowerName);
844      writeField(writer, d, types.get(lowerName), ocNames, false, false,
845           structuralClassName, lazyAttrs.contains(lowerName), terse);
846    }
847
848    for (final String lowerName : operationalAttrs.keySet())
849    {
850      final AttributeTypeDefinition d = operationalAttrs.get(lowerName);
851      final TreeSet<String> ocNames = EMPTY_TREE_SET;
852      writeField(writer, d, types.get(lowerName), ocNames, false, false,
853           structuralClassName, lazyAttrs.contains(lowerName), terse);
854    }
855
856
857    // Add the default constructor.
858    writer.println();
859    writer.println();
860    writer.println();
861    writer.println("  /**");
862    writer.println("   * Creates a new instance of this object.  All fields " +
863         "will be uninitialized,");
864    writer.println("   * so the setter methods should be used to assign " +
865         "values to them.");
866    writer.println("   */");
867    writer.println("  public " + className + "()");
868    writer.println("  {");
869    writer.println("    // No initialization will be performed by default.  " +
870         "Note that if you set");
871    writer.println("    // values for any fields marked with an @LDAPField, " +
872         "@LDAPDNField, or");
873    writer.println("    // @LDAPEntryField annotation, they will be " +
874         "overwritten in the course of");
875    writer.println("    // decoding initializing this object from an LDAP " +
876         "entry.");
877    writer.println("  }");
878
879
880    // Add a static decode method that can create an instance of the object
881    // from a given entry.
882    writer.println();
883    writer.println();
884    writer.println();
885    writer.println("  /**");
886    writer.println("   * Creates a new " + className + " object decoded");
887    writer.println("   * from the provided entry.");
888    writer.println("   *");
889    writer.println("   * @param  entry  The entry to be decoded.");
890    writer.println("   *");
891    writer.println("   * @return  The decoded " + className + " object.");
892    writer.println("   *");
893    writer.println("   * @throws  LDAPPersistException  If a problem occurs " +
894         "while attempting to");
895    writer.println("   *                                decode the provided " +
896         "entry.");
897    writer.println("   */");
898    writer.println("  public static " + className +
899         " decode(final Entry entry)");
900    writer.println("         throws LDAPPersistException");
901    writer.println("  {");
902    writer.println("    return getPersister().decode(entry);");
903    writer.println("  }");
904
905
906    // Add the getPersister method.
907    writer.println("");
908    writer.println("");
909    writer.println("");
910    writer.println("  /**");
911    writer.println("   * Retrieves an {@code LDAPPersister} instance that " +
912         "may be used to interact");
913    writer.println("   * with objects of this type.");
914    writer.println("   *");
915    writer.println("   * @return  An {@code LDAPPersister} instance that may " +
916         "be used to interact");
917    writer.println("   *          with objects of this type.");
918    writer.println("   *");
919    writer.println("   * @throws  LDAPPersistException  If a problem occurs " +
920         "while creating the");
921    writer.println("   *                                " +
922         "{@code LDAPPersister} instance.");
923    writer.println("   */");
924    writer.println("  public static LDAPPersister<" + className +
925         "> getPersister()");
926    writer.println("         throws LDAPPersistException");
927    writer.println("  {");
928    writer.println("    return LDAPPersister.getInstance(" + className +
929         ".class);");
930    writer.println("  }");
931
932
933    // Add the post-decode and post-encode methods.
934    writer.println();
935    writer.println();
936    writer.println();
937    writer.println("  /**");
938    writer.println("   * Performs any processing that may be necessary after " +
939         "initializing this");
940    writer.println("   * object from an LDAP entry.");
941    writer.println("   *");
942    writer.println("   * @throws  LDAPPersistException  If there is a " +
943         "problem with the object after");
944    writer.println("   *                                it has been decoded " +
945         "from an LDAP entry.");
946    writer.println("   */");
947    writer.println("  private void doPostDecode()");
948    writer.println("          throws LDAPPersistException");
949    writer.println("  {");
950    writer.println("    // No processing is needed by default.  You may " +
951         "provide an implementation");
952    writer.println("    // for this method if custom post-decode processing " +
953         "is needed.");
954    writer.println("  }");
955    writer.println();
956    writer.println();
957    writer.println();
958    writer.println("  /**");
959    writer.println("   * Performs any processing that may be necessary after " +
960         "encoding this object");
961    writer.println("   * to an LDAP entry.");
962    writer.println("   *");
963    writer.println("   * @param  entry  The entry that has been generated.  " +
964         "It may be altered if");
965    writer.println("   *                desired.");
966    writer.println("   *");
967    writer.println("   * @throws  LDAPPersistException  If the generated " +
968         "entry should not be used.");
969    writer.println("   */");
970    writer.println("  private void doPostEncode(final Entry entry)");
971    writer.println("          throws LDAPPersistException");
972    writer.println("  {");
973    writer.println("    // No processing is needed by default.  You may " +
974         "provide an implementation");
975    writer.println("    // for this method if custom post-encode processing " +
976         "is needed.");
977    writer.println("  }");
978
979
980    // Add a method for getting a read-only copy of the associated entry.
981    writer.println();
982    writer.println();
983    writer.println();
984    writer.println("  /**");
985    writer.println("   * Retrieves a read-only copy of the entry with which " +
986         "this object is");
987    writer.println("   * associated, if it is available.  It will only be " +
988         "available if this object");
989    writer.println("   * was decoded from or encoded to an LDAP entry.");
990    writer.println("   *");
991    writer.println("   * @return  A read-only copy of the entry with which " +
992         "this object is");
993    writer.println("   *          associated, or {@code null} if it is not " +
994         "available.");
995    writer.println("   */");
996    writer.println("  public ReadOnlyEntry getLDAPEntry()");
997    writer.println("  {");
998    writer.println("    return ldapEntry;");
999    writer.println("  }");
1000
1001
1002    // Add a method for getting the DN of the associated entry.
1003    writer.println();
1004    writer.println();
1005    writer.println();
1006    writer.println("  /**");
1007    writer.println("   * Retrieves the DN of the entry with which this " +
1008         "object is associated, if it");
1009    writer.println("   * is available.  It will only be available if this " +
1010         "object was decoded from or");
1011    writer.println("   * encoded to an LDAP entry.");
1012    writer.println("   *");
1013    writer.println("   * @return  The DN of the entry with which this object " +
1014         "is associated, or");
1015    writer.println("   *          {@code null} if it is not available.");
1016    writer.println("   */");
1017    writer.println("  public String getLDAPEntryDN()");
1018    writer.println("  {");
1019    writer.println("    if (ldapEntry == null)");
1020    writer.println("    {");
1021    writer.println("      return null;");
1022    writer.println("    }");
1023    writer.println("    else");
1024    writer.println("    {");
1025    writer.println("      return ldapEntry.getDN();");
1026    writer.println("    }");
1027    writer.println("  }");
1028
1029
1030    // Add getter, setter, and filter generation methods for all of the fields
1031    // associated with LDAP attributes.  First the fields for the RDN
1032    // attributes, then for the rest of the required attributes, and then for
1033    // the optional attributes.
1034    for (final String lowerName : rdnAttrs)
1035    {
1036      final AttributeTypeDefinition d = requiredAttrs.get(lowerName);
1037      writeFieldMethods(writer, d, types.get(lowerName), true);
1038    }
1039
1040    for (final String lowerName : requiredAttrs.keySet())
1041    {
1042      if (rdnAttrs.contains(lowerName))
1043      {
1044        continue;
1045      }
1046
1047      final AttributeTypeDefinition d = requiredAttrs.get(lowerName);
1048      writeFieldMethods(writer, d, types.get(lowerName), true);
1049    }
1050
1051    for (final String lowerName : optionalAttrs.keySet())
1052    {
1053      final AttributeTypeDefinition d = optionalAttrs.get(lowerName);
1054      writeFieldMethods(writer, d, types.get(lowerName), true);
1055    }
1056
1057    for (final String lowerName : operationalAttrs.keySet())
1058    {
1059      final AttributeTypeDefinition d = operationalAttrs.get(lowerName);
1060      writeFieldMethods(writer, d, types.get(lowerName), false);
1061    }
1062
1063    writeToString(writer, className, requiredAttrs.values(),
1064         optionalAttrs.values(), operationalAttrs.values());
1065
1066    writer.println("}");
1067    writer.println();
1068    writer.close();
1069
1070    return ResultCode.SUCCESS;
1071  }
1072
1073
1074
1075
1076
1077  /**
1078   * Performs an appropriate set of processing for the provided object class to
1079   * ensure that all of the required and optional attributes are classified
1080   * properly.
1081   *
1082   * @param  oc   The object class to process.
1083   * @param  s    The server schema.
1084   * @param  ra   The set of required attributes identified so far.
1085   * @param  rac  The object classes referenced by the required attributes.
1086   * @param  oa   The set of optional attributes identified so far.
1087   * @param  oac  The object classes referenced by the optional attributes.
1088   * @param  t    A map of attribute type names to Java types.
1089   */
1090  void processObjectClass(final ObjectClassDefinition oc, final Schema s,
1091            final TreeMap<String,AttributeTypeDefinition> ra,
1092            final TreeMap<String,TreeSet<String>> rac,
1093            final TreeMap<String,AttributeTypeDefinition> oa,
1094            final TreeMap<String,TreeSet<String>> oac,
1095            final TreeMap<String,String> t)
1096  {
1097    for (final AttributeTypeDefinition d : oc.getRequiredAttributes(s, true))
1098    {
1099      if (d.hasNameOrOID("objectClass"))
1100      {
1101        continue;
1102      }
1103
1104      final String lowerName = toLowerCase(d.getNameOrOID());
1105      if (ra.containsKey(lowerName))
1106      {
1107        rac.get(lowerName).add(oc.getNameOrOID());
1108      }
1109      else if (oa.containsKey(lowerName))
1110      {
1111        oa.remove(lowerName);
1112        ra.put(lowerName, d);
1113
1114        final TreeSet<String> ocSet = oac.remove(lowerName);
1115        ocSet.add(oc.getNameOrOID());
1116        rac.put(lowerName, ocSet);
1117      }
1118      else
1119      {
1120        final TreeSet<String> ocSet = new TreeSet<String>();
1121        ocSet.add(oc.getNameOrOID());
1122        ra.put(lowerName, d);
1123        rac.put(lowerName, ocSet);
1124        t.put(lowerName, getJavaType(s, d));
1125      }
1126    }
1127
1128    for (final AttributeTypeDefinition d : oc.getOptionalAttributes(s, true))
1129    {
1130      if (d.hasNameOrOID("objectClass"))
1131      {
1132        continue;
1133      }
1134
1135      final String lowerName = toLowerCase(d.getNameOrOID());
1136      if (ra.containsKey(lowerName))
1137      {
1138        rac.get(lowerName).add(oc.getNameOrOID());
1139      }
1140      else if (oa.containsKey(lowerName))
1141      {
1142        oac.get(lowerName).add(oc.getNameOrOID());
1143      }
1144      else
1145      {
1146        final TreeSet<String> ocSet = new TreeSet<String>();
1147        ocSet.add(oc.getNameOrOID());
1148        oa.put(lowerName, d);
1149        oac.put(lowerName, ocSet);
1150        t.put(lowerName, getJavaType(s, d));
1151      }
1152    }
1153  }
1154
1155
1156
1157  /**
1158   * Writes information about a field to the Java class file.
1159   *
1160   * @param  writer    The writer to which the field information should be
1161   *                   written.
1162   * @param  d         The attribute type definition.
1163   * @param  type      The name of the Java type to use for the field.
1164   * @param  ocNames   The names of the object classes for the attribute type.
1165   * @param  inRDN     Indicates whether the attribute should be included in
1166   *                   generated entry RDNs.
1167   * @param  required  Indicates whether the attribute should be considered
1168   *                   required.
1169   * @param  sc        The name of the structural object class for the object.
1170   * @param  lazy      Indicates whether the field should be marked for lazy
1171   *                   loading.
1172   * @param  terse     Indicates whether to use terse mode.
1173   */
1174  static void writeField(final PrintWriter writer,
1175                         final AttributeTypeDefinition d, final String type,
1176                         final TreeSet<String> ocNames,
1177                         final boolean inRDN, final boolean required,
1178                         final String sc, final boolean lazy,
1179                         final boolean terse)
1180  {
1181    final String attrName  = d.getNameOrOID();
1182    final String fieldName = PersistUtils.toJavaIdentifier(attrName);
1183
1184    writer.println();
1185
1186    if (inRDN)
1187    {
1188      writer.println("  // The field used for RDN attribute " + attrName + '.');
1189    }
1190    else if (required)
1191    {
1192      writer.println("  // The field used for required attribute " + attrName +
1193           '.');
1194    }
1195    else if (d.isOperational())
1196    {
1197      writer.println("  // The field used for operational attribute " +
1198           attrName + '.');
1199    }
1200    else
1201    {
1202      writer.println("  // The field used for optional attribute " + attrName +
1203           '.');
1204    }
1205
1206    boolean added = false;
1207    if (terse && attrName.equalsIgnoreCase(fieldName))
1208    {
1209      writer.print("  @LDAPField(");
1210    }
1211    else
1212    {
1213      writer.print("  @LDAPField(attribute=\"" + attrName + '"');
1214      added = true;
1215    }
1216
1217    if (ocNames.isEmpty())
1218    {
1219      // Don't need to do anything.  This should only be the case for
1220      // operational attributes.
1221    }
1222    else if (ocNames.size() == 1)
1223    {
1224      if ((! terse) || (! ocNames.iterator().next().equalsIgnoreCase(sc)))
1225      {
1226        if (added)
1227        {
1228          writer.println(",");
1229          writer.print("             objectClass=\"" +
1230               ocNames.iterator().next() + '"');
1231        }
1232        else
1233        {
1234          writer.println("objectClass=\"" +
1235               ocNames.iterator().next() + '"');
1236          added = true;
1237        }
1238      }
1239    }
1240    else
1241    {
1242      final Iterator<String> iterator = ocNames.iterator();
1243      if (added)
1244      {
1245        writer.println(",");
1246        writer.println("             objectClass={ \"" +
1247             iterator.next() + "\",");
1248      }
1249      else
1250      {
1251        writer.println("objectClass={ \"" +
1252             iterator.next() + "\",");
1253        added = true;
1254      }
1255
1256      while (iterator.hasNext())
1257      {
1258        final String name = iterator.next();
1259        if (iterator.hasNext())
1260        {
1261          writer.println("                           \"" + name + "\",");
1262        }
1263        else
1264        {
1265          writer.print("                           \"" + name + "\" }");
1266        }
1267      }
1268    }
1269
1270    if (inRDN)
1271    {
1272      if (added)
1273      {
1274        writer.println(",");
1275        writer.println("             inRDN=true,");
1276      }
1277      else
1278      {
1279        writer.println("inRDN=true,");
1280        added = true;
1281      }
1282      writer.print("             filterUsage=FilterUsage.ALWAYS_ALLOWED");
1283    }
1284    else
1285    {
1286      if (! terse)
1287      {
1288        if (added)
1289        {
1290          writer.println(",");
1291          writer.print("             " +
1292               "filterUsage=FilterUsage.CONDITIONALLY_ALLOWED");
1293        }
1294        else
1295        {
1296          writer.print("filterUsage=FilterUsage.CONDITIONALLY_ALLOWED");
1297          added = true;
1298        }
1299      }
1300    }
1301
1302    if (required)
1303    {
1304      if (added)
1305      {
1306        writer.println(",");
1307        writer.print("             requiredForEncode=true");
1308      }
1309      else
1310      {
1311        writer.print("requiredForEncode=true");
1312        added = true;
1313      }
1314    }
1315
1316    if (d.isOperational())
1317    {
1318      if (added)
1319      {
1320        writer.println(",");
1321        writer.println("             inAdd=false,");
1322      }
1323      else
1324      {
1325        writer.println("inAdd=false,");
1326        added = true;
1327      }
1328
1329      writer.print("             inModify=false");
1330    }
1331
1332    if (lazy)
1333    {
1334      if (added)
1335      {
1336        writer.println(",");
1337        writer.print("             lazilyLoad=true");
1338      }
1339      else
1340      {
1341        writer.print("lazilyLoad=true");
1342        added = true;
1343      }
1344    }
1345
1346    writer.println(")");
1347    if (d.isSingleValued())
1348    {
1349      writer.println("  private " + type + ' ' + fieldName + ';');
1350    }
1351    else
1352    {
1353      writer.println("  private " + type + "[] " + fieldName + ';');
1354    }
1355  }
1356
1357
1358
1359  /**
1360   * Writes getter, setter, and filter creation methods for the specified
1361   * attribute.
1362   *
1363   * @param  writer     The writer to use to write the methods.
1364   * @param  d          The attribute type definition to be written.
1365   * @param  type       The name of the Java type to use for the attribute.
1366   * @param  addSetter  Indicates whether to write a setter method.
1367   */
1368  static void writeFieldMethods(final PrintWriter writer,
1369                                final AttributeTypeDefinition d,
1370                                final String type, final boolean addSetter)
1371  {
1372    writer.println();
1373    writer.println();
1374    writer.println();
1375
1376    final String attrName  = d.getNameOrOID();
1377    final String fieldName = PersistUtils.toJavaIdentifier(attrName);
1378    final String capFieldName = capitalize(fieldName);
1379
1380    if (d.isSingleValued())
1381    {
1382      if (type.equals("DN"))
1383      {
1384        writer.println("  /**");
1385        writer.println("   * Retrieves the first value for the field " +
1386             "associated with the");
1387        writer.println("   * " + attrName + " attribute as a DN, if present.");
1388        writer.println("   *");
1389        writer.println("   * @return  The first value for the field " +
1390             "associated with the");
1391        writer.println("   *          " + attrName + " attribute, or");
1392        writer.println("   *          {@code null} if the field does not " +
1393             "have a value.");
1394        writer.println("   */");
1395        writer.println("  public DN get" + capFieldName + "DN()");
1396        writer.println("  {");
1397        writer.println("    return " + fieldName + ';');
1398        writer.println("  }");
1399
1400        writer.println();
1401        writer.println();
1402        writer.println();
1403
1404        writer.println("  /**");
1405        writer.println("   * Retrieves the object referenced by the DN held " +
1406             "in the");
1407        writer.println("   * " + attrName + " attribute, if present.");
1408        writer.println("   *");
1409        writer.println("   * @param  <T>  The type of object to return.");
1410        writer.println("   *");
1411        writer.println("   * @param  connection  The connection to use to " +
1412             "retrieve the entry.  It must");
1413        writer.println("   *                     not be {@code null}.");
1414        writer.println("   * @param  type        The type of object as which " +
1415             "to decode the entry.  It");
1416        writer.println("   *                     must not be {@code null}, " +
1417             "and the class must be marked");
1418        writer.println("   *                     with the {@code LDAPObject} " +
1419             "annotation type.");
1420        writer.println("   *");
1421        writer.println("   * @return  The object decoded from the entry with " +
1422             "the associated DN, or");
1423        writer.println("   *          {@code null} if the field does not " +
1424             "have a value or the referenced");
1425        writer.println("   *          entry does not exist.");
1426        writer.println("   *");
1427        writer.println("   * @throws  LDAPException  If a problem occurs " +
1428             "while attempting to retrieve");
1429        writer.println("   *                         the entry or decode it " +
1430             "as an object of the");
1431        writer.println("   *                         specified type.");
1432        writer.println("   */");
1433        writer.println("  public <T> T get" + capFieldName + "Object(");
1434        writer.println("                    final LDAPInterface connection,");
1435        writer.println("                    final Class<T> type)");
1436        writer.println("         throws LDAPException");
1437        writer.println("  {");
1438        writer.println("    return PersistUtils.getEntryAsObject(" + fieldName +
1439             ',');
1440        writer.println("         type, connection);");
1441        writer.println("  }");
1442
1443        if (addSetter)
1444        {
1445          writer.println();
1446          writer.println();
1447          writer.println();
1448
1449          writer.println("  /**");
1450          writer.println("   * Sets the value for the field associated with " +
1451               "the");
1452          writer.println("   * " + attrName + " attribute.");
1453          writer.println("   *");
1454          writer.println("   * @param  v  The value for the field associated " +
1455               "with the");
1456          writer.println("   *            " + attrName + " attribute.");
1457          writer.println("   */");
1458          writer.println("  public void set" + capFieldName + "(final DN v)");
1459          writer.println("  {");
1460          writer.println("    this." + fieldName + " = v;");
1461          writer.println("  }");
1462
1463          writer.println();
1464          writer.println();
1465          writer.println();
1466
1467          writer.println("  /**");
1468          writer.println("   * Sets the value for the field associated with " +
1469               "the");
1470          writer.println("   * " + attrName + " attribute.");
1471          writer.println("   *");
1472          writer.println("   * @param  v  The string representation of the " +
1473               "value for the field associated");
1474          writer.println("   *            with the " + attrName +
1475               " attribute.");
1476          writer.println("   *");
1477          writer.println("   * @throws  LDAPException  If the provided " +
1478               "string cannot be parsed as a DN.");
1479          writer.println("   */");
1480          writer.println("  public void set" + capFieldName +
1481               "(final String v)");
1482          writer.println("         throws LDAPException");
1483          writer.println("  {");
1484          writer.println("    if (v == null)");
1485          writer.println("    {");
1486          writer.println("      this." + fieldName + " = null;");
1487          writer.println("    }");
1488          writer.println("    else");
1489          writer.println("    {");
1490          writer.println("      this." + fieldName + " = new DN(v);");
1491          writer.println("    }");
1492          writer.println("  }");
1493        }
1494      }
1495      else
1496      {
1497        writer.println("  /**");
1498        writer.println("   * Retrieves the value for the field associated " +
1499             "with the");
1500        writer.println("   * " + attrName + " attribute, if present.");
1501        writer.println("   *");
1502        writer.println("   * @return  The value for the field associated " +
1503             "with the");
1504        writer.println("   *          " + attrName + " attribute, or");
1505        writer.println("   *          {@code null} if the field does not " +
1506             "have a value.");
1507        writer.println("   */");
1508        writer.println("  public " + type + " get" + capFieldName + "()");
1509        writer.println("  {");
1510        writer.println("    return " + fieldName + ';');
1511        writer.println("  }");
1512
1513        if (addSetter)
1514        {
1515          writer.println();
1516          writer.println();
1517          writer.println();
1518
1519          writer.println("  /**");
1520          writer.println("   * Sets the value for the field associated with " +
1521               "the");
1522          writer.println("   * " + attrName + " attribute.");
1523          writer.println("   *");
1524          writer.println("   * @param  v  The value for the field associated " +
1525               "with the");
1526          writer.println("   *            " + attrName + " attribute.");
1527          writer.println("   */");
1528          writer.println("  public void set" + capFieldName + "(final " + type +
1529               " v)");
1530          writer.println("  {");
1531          writer.println("    this." + fieldName + " = v;");
1532          writer.println("  }");
1533        }
1534      }
1535    }
1536    else
1537    {
1538      if (type.equals("DN"))
1539      {
1540        writer.println("  /**");
1541        writer.println("   * Retrieves the first value for the field " +
1542             "associated with the");
1543        writer.println("   * " + attrName + " attribute as a DN, if present.");
1544        writer.println("   *");
1545        writer.println("   * @return  The first value for the field " +
1546             "associated with the");
1547        writer.println("   *          " + attrName + " attribute, or");
1548        writer.println("   *          {@code null} if that attribute was not " +
1549             "present in the entry or");
1550        writer.println("   *          does not have any values.");
1551        writer.println("   */");
1552        writer.println("  public DN getFirst" + capFieldName + "DN()");
1553        writer.println("  {");
1554        writer.println("    if ((" + fieldName + " == null) ||");
1555        writer.println("        (" + fieldName + ".length == 0))");
1556        writer.println("    {");
1557        writer.println("      return null;");
1558        writer.println("    }");
1559        writer.println("    else");
1560        writer.println("    {");
1561        writer.println("      return " + fieldName + "[0];");
1562        writer.println("    }");
1563        writer.println("  }");
1564
1565        writer.println();
1566        writer.println();
1567        writer.println();
1568
1569        writer.println("  /**");
1570        writer.println("   * Retrieves the values for the field associated " +
1571             "with the");
1572        writer.println("   * " + attrName + " attribute as DNs, if present.");
1573        writer.println("   *");
1574        writer.println("   * @return  The values for the field associated " +
1575             "with the");
1576        writer.println("   *          " + attrName + " attribute, or");
1577        writer.println("   *          {@code null} if that attribute was not " +
1578             "present in the entry.");
1579        writer.println("   */");
1580        writer.println("  public DN[] get" + capFieldName + "DNs()");
1581        writer.println("  {");
1582        writer.println("    return " + fieldName + ';');
1583        writer.println("  }");
1584
1585        writer.println();
1586        writer.println();
1587        writer.println();
1588
1589        writer.println("  /**");
1590        writer.println("   * Retrieves the values for the field associated " +
1591             "with the");
1592        writer.println("   * " + attrName + " attribute as objects of the " +
1593             "specified type,");
1594        writer.println("   * if present.");
1595        writer.println("   *");
1596        writer.println("   * @param  <T>  The type of object to return.");
1597        writer.println("   *");
1598        writer.println("   * @param  connection  The connection to use to " +
1599             "retrieve the entries.  It");
1600        writer.println("   *                     must not be {@code null}.");
1601        writer.println("   * @param  type        The type of object as which " +
1602             "the entries should be");
1603        writer.println("   *                     decoded.  It must not be " +
1604             "{@code null}, and the class");
1605        writer.println("   *                     must be marked with the " +
1606             "{@code LDAPObject} annotation");
1607        writer.println("   *                     type.");
1608        writer.println("   *");
1609        writer.println("   * @return  A {@code PersistedObjects} object that " +
1610             "may be used to iterate");
1611        writer.println("   *          across the resulting objects.");
1612        writer.println("   *");
1613        writer.println("   * @throws  LDAPException  If the requested type " +
1614             "cannot be used with the LDAP");
1615        writer.println("   *                         SDK persistence " +
1616             "framework.");
1617        writer.println("   */");
1618        writer.println("  public <T> PersistedObjects<T> get" + capFieldName +
1619             "Objects(");
1620        writer.println("                                      final " +
1621             "LDAPInterface connection,");
1622        writer.println("                                      final Class<T> " +
1623             "type)");
1624        writer.println("         throws LDAPException");
1625        writer.println("  {");
1626        writer.println("    return PersistUtils.getEntriesAsObjects(" +
1627             fieldName + ',');
1628        writer.println("         type, connection);");
1629        writer.println("  }");
1630
1631        if (addSetter)
1632        {
1633          writer.println();
1634          writer.println();
1635          writer.println();
1636
1637          writer.println("  /**");
1638          writer.println("   * Sets the values for the field associated with " +
1639               "the");
1640          writer.println("   * " + attrName + " attribute.");
1641          writer.println("   *");
1642          writer.println("   * @param  v  The values for the field " +
1643               "associated with the");
1644          writer.println("   *            " + attrName + " attribute.");
1645          writer.println("   */");
1646          writer.println("  public void set" + capFieldName +
1647               "(final DN... v)");
1648          writer.println("  {");
1649          writer.println("    this." + fieldName + " = v;");
1650          writer.println("  }");
1651
1652          writer.println();
1653          writer.println();
1654          writer.println();
1655
1656          writer.println("  /**");
1657          writer.println("   * Sets the values for the field associated with " +
1658               "the");
1659          writer.println("   * " + attrName + " attribute.");
1660          writer.println("   *");
1661          writer.println("   * @param  v  The string representations of the " +
1662               "values for the field");
1663          writer.println("   *            associated with the " + attrName +
1664               " attribute.");
1665          writer.println("   *");
1666          writer.println("   * @throws  LDAPException  If any of the " +
1667               "provided strings cannot be parsed as");
1668          writer.println("   *                         a DN.");
1669          writer.println("   */");
1670          writer.println("  public void set" + capFieldName +
1671               "(final String... v)");
1672          writer.println("         throws LDAPException");
1673          writer.println("  {");
1674          writer.println("    if (v == null)");
1675          writer.println("    {");
1676          writer.println("      this." + fieldName + " = null;");
1677          writer.println("    }");
1678          writer.println("    else");
1679          writer.println("    {");
1680          writer.println("      this." + fieldName + " = new DN[v.length];");
1681          writer.println("      for (int i=0; i < v.length; i++)");
1682          writer.println("      {");
1683          writer.println("        this." + fieldName + "[i] = new DN(v[i]);");
1684          writer.println("      }");
1685          writer.println("    }");
1686          writer.println("  }");
1687        }
1688      }
1689      else
1690      {
1691        writer.println("  /**");
1692        writer.println("   * Retrieves the first value for the field " +
1693             "associated with the");
1694        writer.println("   * " + attrName + " attribute, if present.");
1695        writer.println("   *");
1696        writer.println("   * @return  The first value for the field " +
1697             "associated with the");
1698        writer.println("   *          " + attrName + " attribute, or");
1699        writer.println("   *          {@code null} if that attribute was not " +
1700             "present in the entry or");
1701        writer.println("   *          does not have any values.");
1702        writer.println("   */");
1703        writer.println("  public " + type + " getFirst" + capFieldName + "()");
1704        writer.println("  {");
1705        writer.println("    if ((" + fieldName + " == null) ||");
1706        writer.println("        (" + fieldName + ".length == 0))");
1707        writer.println("    {");
1708        writer.println("      return null;");
1709        writer.println("    }");
1710        writer.println("    else");
1711        writer.println("    {");
1712        writer.println("      return " + fieldName + "[0];");
1713        writer.println("    }");
1714        writer.println("  }");
1715
1716        writer.println();
1717        writer.println();
1718        writer.println();
1719
1720        writer.println("  /**");
1721        writer.println("   * Retrieves the values for the field associated " +
1722             "with the");
1723        writer.println("   * " + attrName + " attribute, if present.");
1724        writer.println("   *");
1725        writer.println("   * @return  The values for the field associated " +
1726             "with the");
1727        writer.println("   *          " + attrName + " attribute, or");
1728        writer.println("   *          {@code null} if that attribute was not " +
1729             "present in the entry.");
1730        writer.println("   */");
1731        writer.println("  public " + type + "[] get" + capFieldName + "()");
1732        writer.println("  {");
1733        writer.println("    return " + fieldName + ';');
1734        writer.println("  }");
1735
1736        if (addSetter)
1737        {
1738          writer.println();
1739          writer.println();
1740          writer.println();
1741
1742          writer.println("  /**");
1743          writer.println("   * Sets the values for the field associated with " +
1744               "the");
1745          writer.println("   * " + attrName + " attribute.");
1746          writer.println("   *");
1747          writer.println("   * @param  v  The values for the field " +
1748               "associated with the");
1749          writer.println("   *            " + attrName + " attribute.");
1750          writer.println("   */");
1751          writer.println("  public void set" + capFieldName + "(final " + type +
1752               "... v)");
1753          writer.println("  {");
1754          writer.println("    this." + fieldName + " = v;");
1755          writer.println("  }");
1756        }
1757      }
1758    }
1759
1760
1761    writer.println();
1762    writer.println();
1763    writer.println();
1764
1765    writer.println("  /**");
1766    writer.println("   * Generates a filter that may be used to search for " +
1767         "objects of this type");
1768    writer.println("   * using the " + attrName + " attribute.");
1769    writer.println("   * The resulting filter may be combined with other " +
1770         "filter elements to create a");
1771    writer.println("   * more complex filter.");
1772    writer.println("   *");
1773    writer.println("   * @param  filterType  The type of filter to generate.");
1774    writer.println("   * @param  value       The value to use to use for the " +
1775         "filter.  It may be");
1776    writer.println("   *                     {@code null} only for a filter " +
1777         "type of");
1778    writer.println("   *                     {@code PRESENCE}.");
1779    writer.println("   *");
1780    writer.println("   * @return  The generated search filter.");
1781    writer.println("   *");
1782    writer.println("   * @throws  LDAPPersistException  If a problem is " +
1783         "encountered while attempting");
1784    writer.println("   *                                to generate the " +
1785         "filter.");
1786    writer.println("   */");
1787    writer.println("  public static Filter generate" + capFieldName +
1788         "Filter(");
1789    writer.println("                            final PersistFilterType " +
1790         "filterType,");
1791    writer.println("                            final " + type + " value)");
1792    writer.println("         throws LDAPPersistException");
1793    writer.println("  {");
1794    writer.println("    final byte[] valueBytes;");
1795    writer.println("    if (filterType == PersistFilterType.PRESENCE)");
1796    writer.println("    {");
1797    writer.println("      valueBytes = null;");
1798    writer.println("    }");
1799    writer.println("    else");
1800    writer.println("    {");
1801    writer.println("      if (value == null)");
1802    writer.println("      {");
1803    writer.println("        throw new LDAPPersistException(\"Unable to " +
1804         "generate a filter of type \" +");
1805    writer.println("             filterType.name() + \" with a null value " +
1806         "for attribute \" +");
1807    writer.println("             \"" + attrName + "\");");
1808    writer.println("      }");
1809    writer.println();
1810    writer.println("      final LDAPObjectHandler<?> objectHandler =");
1811    writer.println("           getPersister().getObjectHandler();");
1812    writer.println("      final FieldInfo fieldInfo = " +
1813         "objectHandler.getFields().get(");
1814    writer.println("           \"" + toLowerCase(attrName) + "\");");
1815    writer.println();
1816    writer.println("      final DefaultObjectEncoder objectEncoder = new " +
1817         "DefaultObjectEncoder();");
1818    writer.println("      valueBytes = " +
1819         "objectEncoder.encodeFieldValue(fieldInfo.getField(),");
1820
1821    if (d.isSingleValued())
1822    {
1823      writer.println("           value,");
1824    }
1825    else
1826    {
1827      writer.println("           new " + type + "[] { value },");
1828    }
1829
1830    writer.println("           \"" + attrName + "\").getValueByteArray();");
1831    writer.println("    }");
1832    writer.println();
1833    writer.println("    switch (filterType)");
1834    writer.println("    {");
1835    writer.println("      case PRESENCE:");
1836    writer.println("        return Filter.createPresenceFilter(");
1837    writer.println("             \"" + attrName + "\");");
1838    writer.println("      case EQUALITY:");
1839    writer.println("        return Filter.createEqualityFilter(");
1840    writer.println("             \"" + attrName + "\",");
1841    writer.println("             valueBytes);");
1842    writer.println("      case STARTS_WITH:");
1843    writer.println("        return Filter.createSubstringFilter(");
1844    writer.println("             \"" + attrName + "\",");
1845    writer.println("             valueBytes, null, null);");
1846    writer.println("      case ENDS_WITH:");
1847    writer.println("        return Filter.createSubstringFilter(");
1848    writer.println("             \"" + attrName + "\",");
1849    writer.println("             null, null, valueBytes);");
1850    writer.println("      case CONTAINS:");
1851    writer.println("        return Filter.createSubstringFilter(");
1852    writer.println("             \"" + attrName + "\",");
1853    writer.println("             null, new byte[][] { valueBytes }, null);");
1854    writer.println("      case GREATER_OR_EQUAL:");
1855    writer.println("        return Filter.createGreaterOrEqualFilter(");
1856    writer.println("             \"" + attrName + "\",");
1857    writer.println("             valueBytes);");
1858    writer.println("      case LESS_OR_EQUAL:");
1859    writer.println("        return Filter.createLessOrEqualFilter(");
1860    writer.println("             \"" + attrName + "\",");
1861    writer.println("             valueBytes);");
1862    writer.println("      case APPROXIMATELY_EQUAL_TO:");
1863    writer.println("        return Filter.createApproximateMatchFilter(");
1864    writer.println("             \"" + attrName + "\",");
1865    writer.println("             valueBytes);");
1866    writer.println("      default:");
1867    writer.println("        // This should never happen.");
1868    writer.println("        throw new LDAPPersistException(\"Unrecognized " +
1869         "filter type \" +");
1870    writer.println("             filterType.name());");
1871    writer.println("    }");
1872    writer.println("  }");
1873  }
1874
1875
1876
1877  /**
1878   * Writes a {@code toString} method for the generated class.
1879   *
1880   * @param  writer            The writer to use to write the methods.
1881   * @param  className         The base name (without package information) for
1882   *                           the generated class.
1883   * @param  requiredAttrs     The set of required attributes for the generated
1884   *                           class.
1885   * @param  optionalAttrs     The set of optional attributes for the generated
1886   *                           class.
1887   * @param  operationalAttrs  The set of operational attributes for the
1888   *                           generated class.
1889   */
1890  static void writeToString(final PrintWriter writer, final String className,
1891                   final Collection<AttributeTypeDefinition> requiredAttrs,
1892                   final Collection<AttributeTypeDefinition> optionalAttrs,
1893                   final Collection<AttributeTypeDefinition> operationalAttrs)
1894  {
1895    writer.println();
1896    writer.println();
1897    writer.println();
1898    writer.println("  /**");
1899    writer.println("   * Retrieves a string representation of this");
1900    writer.println("   * {@code " + className + "} object.");
1901    writer.println("   *");
1902    writer.println("   * @return  A string representation of this");
1903    writer.println("   *          {@code " + className + "} object.");
1904    writer.println("   */");
1905    writer.println("  @Override()");
1906    writer.println("  public String toString()");
1907    writer.println("  {");
1908    writer.println("    final StringBuilder buffer = new StringBuilder();");
1909    writer.println("    toString(buffer);");
1910    writer.println("    return buffer.toString();");
1911    writer.println("  }");
1912
1913    writer.println();
1914    writer.println();
1915    writer.println();
1916    writer.println("  /**");
1917    writer.println("   * Appends a string representation of this");
1918    writer.println("   * {@code " + className + "} object");
1919    writer.println("   * to the provided buffer.");
1920    writer.println("   *");
1921    writer.println("   * @param  buffer  The buffer to which the string " +
1922         "representation should be");
1923    writer.println("   *                 appended.");
1924    writer.println("   */");
1925    writer.println("  public void toString(final StringBuilder buffer)");
1926    writer.println("  {");
1927    writer.println("    buffer.append(\"" + className + "(\");");
1928    writer.println();
1929    writer.println("    boolean appended = false;");
1930    writer.println("    if (ldapEntry != null)");
1931    writer.println("    {");
1932    writer.println("      appended = true;");
1933    writer.println("      buffer.append(\"entryDN='\");");
1934    writer.println("      buffer.append(ldapEntry.getDN());");
1935    writer.println("      buffer.append('\\'');");
1936    writer.println("    }");
1937
1938    for (final AttributeTypeDefinition d : requiredAttrs)
1939    {
1940      writeToStringField(writer, d);
1941    }
1942
1943    for (final AttributeTypeDefinition d : optionalAttrs)
1944    {
1945      writeToStringField(writer, d);
1946    }
1947
1948    for (final AttributeTypeDefinition d : operationalAttrs)
1949    {
1950      writeToStringField(writer, d);
1951    }
1952
1953    writer.println();
1954    writer.println("    buffer.append(')');");
1955    writer.println("  }");
1956  }
1957
1958
1959
1960  /**
1961   * Writes information about the provided field for use in the {@code toString}
1962   * method.
1963   *
1964   * @param  w  The writer to use to write the {@code toString} content.
1965   * @param  d  The attribute type definition for the field to write.
1966   */
1967  private static void writeToStringField(final PrintWriter w,
1968                                         final AttributeTypeDefinition d)
1969  {
1970    final String fieldName = PersistUtils.toJavaIdentifier(d.getNameOrOID());
1971    w.println();
1972    w.println("    if (" +  fieldName + " != null)");
1973    w.println("    {");
1974    w.println("      if (appended)");
1975    w.println("      {");
1976    w.println("        buffer.append(\", \");");
1977    w.println("      }");
1978    w.println("      appended = true;");
1979    w.println("      buffer.append(\"" + fieldName + "=\");");
1980    if (d.isSingleValued())
1981    {
1982      w.println("      buffer.append(" + fieldName + ");");
1983    }
1984    else
1985    {
1986      w.println("      buffer.append(Arrays.toString(" + fieldName + "));");
1987    }
1988    w.println("    }");
1989  }
1990
1991
1992
1993  /**
1994   * Retrieves the Java type to use for the provided attribute type definition.
1995   * For multi-valued attributes, the value returned will be the base type
1996   * without square brackets to indicate an array.
1997   *
1998   * @param  schema  The schema to use to determine the syntax for the
1999   *                 attribute.
2000   * @param  d       The attribute type definition for which to get the Java
2001   *                 type.
2002   *
2003   * @return  The Java type to use for the provided attribute type definition.
2004   */
2005  String getJavaType(final Schema schema, final AttributeTypeDefinition d)
2006  {
2007    if (! d.isSingleValued())
2008    {
2009      needArrays = true;
2010    }
2011
2012    final String syntaxOID = d.getSyntaxOID(schema);
2013    if (syntaxOID == null)
2014    {
2015      return "String";
2016    }
2017
2018    final String oid;
2019    final int bracePos = syntaxOID.indexOf('{');
2020    if (bracePos > 0)
2021    {
2022      oid = syntaxOID.substring(0, bracePos);
2023    }
2024    else
2025    {
2026      oid = syntaxOID;
2027    }
2028
2029    if (oid.equals("1.3.6.1.4.1.1466.115.121.1.7"))
2030    {
2031      // Boolean
2032      return "Boolean";
2033    }
2034    else if (oid.equals("1.3.6.1.4.1.4203.1.1.2") ||
2035             oid.equals("1.3.6.1.4.1.1466.115.121.1.5") ||
2036             oid.equals("1.3.6.1.4.1.1466.115.121.1.8") ||
2037             oid.equals("1.3.6.1.4.1.1466.115.121.1.9") ||
2038             oid.equals("1.3.6.1.4.1.1466.115.121.1.10") ||
2039             oid.equals("1.3.6.1.4.1.1466.115.121.1.28") ||
2040             oid.equals("1.3.6.1.4.1.1466.115.121.1.40"))
2041    {
2042      // auth password
2043      // binary
2044      // certificate
2045      // certificate list
2046      // certificate pair
2047      // JPEG
2048      // octet string
2049      return "byte[]";
2050    }
2051    else if (oid.equals("1.3.6.1.4.1.1466.115.121.1.24"))
2052    {
2053      // generalized time.
2054      needDate = true;
2055      return "Date";
2056    }
2057    else if (oid.equals("1.3.6.1.4.1.1466.115.121.1.27"))
2058    {
2059      // integer
2060      return "Long";
2061    }
2062    else if (oid.equals("1.3.6.1.4.1.1466.115.121.1.12") ||
2063             oid.equals("1.3.6.1.4.1.1466.115.121.1.34"))
2064    {
2065      // DN
2066      // name and optional UID
2067      needDN = true;
2068      if (! d.isSingleValued())
2069      {
2070        needPersistedObjects = true;
2071      }
2072      return "DN";
2073    }
2074    else
2075    {
2076      return "String";
2077    }
2078  }
2079
2080
2081
2082  /**
2083   * {@inheritDoc}
2084   */
2085  @Override()
2086  public LinkedHashMap<String[],String> getExampleUsages()
2087  {
2088    final LinkedHashMap<String[],String> examples =
2089         new LinkedHashMap<String[],String>(1);
2090
2091    final String[] args =
2092    {
2093      "--hostname", "server.example.com",
2094      "--port", "389",
2095      "--bindDN", "uid=admin,dc=example,dc=com",
2096      "--bindPassword", "password",
2097      "--outputDirectory", "src/com/example",
2098      "--structuralClass", "myStructuralClass",
2099      "--auxiliaryClass", "auxClass1",
2100      "--auxiliaryClass", "auxClass2",
2101      "--rdnAttribute", "cn",
2102      "--defaultParentDN", "dc=example,dc=com",
2103      "--packageName", "com.example",
2104      "--className", "MyObject"
2105    };
2106    examples.put(args, INFO_GEN_SOURCE_EXAMPLE_1.get());
2107
2108    return examples;
2109  }
2110}