001    /*
002     * Copyright 2009-2015 UnboundID Corp.
003     * All Rights Reserved.
004     */
005    /*
006     * Copyright (C) 2009-2015 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     */
021    package com.unboundid.ldap.sdk.persist;
022    
023    
024    
025    import java.io.File;
026    import java.io.FileWriter;
027    import java.io.OutputStream;
028    import java.io.PrintWriter;
029    import java.io.Serializable;
030    import java.util.Arrays;
031    import java.util.Collection;
032    import java.util.Date;
033    import java.util.Iterator;
034    import java.util.LinkedHashMap;
035    import java.util.TreeMap;
036    import java.util.TreeSet;
037    
038    import com.unboundid.ldap.sdk.DN;
039    import com.unboundid.ldap.sdk.Entry;
040    import com.unboundid.ldap.sdk.Filter;
041    import com.unboundid.ldap.sdk.LDAPConnection;
042    import com.unboundid.ldap.sdk.LDAPException;
043    import com.unboundid.ldap.sdk.LDAPInterface;
044    import com.unboundid.ldap.sdk.ReadOnlyEntry;
045    import com.unboundid.ldap.sdk.ResultCode;
046    import com.unboundid.ldap.sdk.Version;
047    import com.unboundid.ldap.sdk.schema.AttributeTypeDefinition;
048    import com.unboundid.ldap.sdk.schema.ObjectClassDefinition;
049    import com.unboundid.ldap.sdk.schema.ObjectClassType;
050    import com.unboundid.ldap.sdk.schema.Schema;
051    import com.unboundid.util.LDAPCommandLineTool;
052    import com.unboundid.util.Mutable;
053    import com.unboundid.util.ThreadSafety;
054    import com.unboundid.util.ThreadSafetyLevel;
055    import com.unboundid.util.args.ArgumentException;
056    import com.unboundid.util.args.ArgumentParser;
057    import com.unboundid.util.args.BooleanArgument;
058    import com.unboundid.util.args.DNArgument;
059    import com.unboundid.util.args.FileArgument;
060    import com.unboundid.util.args.StringArgument;
061    
062    import static com.unboundid.ldap.sdk.persist.PersistMessages.*;
063    import static com.unboundid.util.Debug.*;
064    import 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)
075    public 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    }