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