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