private void writeApis(Collection<ApiDeclaration> apis) throws IOException {

    List<ResourceListingAPI> resources = new LinkedList<ResourceListingAPI>();
    File outputDirectory = this.options.getOutputDirectory();
    Recorder recorder = this.options.getRecorder();
    for (ApiDeclaration api : apis) {
      String resourcePath = api.getResourcePath();
      if (!Strings.isNullOrEmpty(resourcePath)) {
        // make sure the filename for the resource is valid
        String resourceFile = ParserHelper.generateResourceFilename(resourcePath);
        resources.add(
            new ResourceListingAPI("/" + resourceFile + ".{format}", api.getDescription()));
        File apiFile = new File(outputDirectory, resourceFile + ".json");
        recorder.record(apiFile, api);
      }
    }

    // write out json for the resource listing
    ResourceListing listing =
        new ResourceListing(
            SWAGGER_VERSION,
            this.options.getApiVersion(),
            this.options.getDocBasePath(),
            resources,
            this.options.getApiAuthorizations(),
            this.options.getApiInfo());
    File docFile = new File(outputDirectory, "service.json");
    recorder.record(docFile, listing);
  }
  public boolean run() {
    try {

      // setup additional classes needed for processing, generally these are java ones such as
      // java.lang.String
      // adding them here allows them to be used in @outputType
      Collection<ClassDoc> typeClasses = new ArrayList<ClassDoc>();
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.lang.String.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.lang.Integer.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.lang.Boolean.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.lang.Float.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.lang.Double.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.lang.Character.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.lang.Long.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.lang.Byte.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.util.Date.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.util.Calendar.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.util.Map.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.util.Collection.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.util.Set.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.util.List.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.math.BigInteger.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.math.BigDecimal.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.util.UUID.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.time.DayOfWeek.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.time.Duration.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.time.Instant.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.time.LocalDate.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.time.LocalDateTime.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.time.Month.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.time.MonthDay.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.time.OffsetDateTime.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.time.OffsetTime.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.time.Period.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.time.Year.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.time.YearMonth.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.time.ZoneId.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.time.ZoneOffset.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.time.ZonedDateTime.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.net.URI.class.getName()));
      addIfNotNull(typeClasses, this.rootDoc.classNamed(java.net.URL.class.getName()));

      // filter the classes to process
      Collection<ClassDoc> docletClasses = new ArrayList<ClassDoc>();
      for (ClassDoc classDoc : this.rootDoc.classes()) {

        // see if excluded via its FQN
        boolean excludeResource = false;
        if (this.options.getExcludeResourcePrefixes() != null
            && !this.options.getExcludeResourcePrefixes().isEmpty()) {
          for (String prefix : this.options.getExcludeResourcePrefixes()) {
            String className = classDoc.qualifiedName();
            if (className.startsWith(prefix)) {
              excludeResource = true;
              break;
            }
          }
        }

        // see if the inclusion filter is set and if so this resource must match this
        if (!excludeResource
            && this.options.getIncludeResourcePrefixes() != null
            && !this.options.getIncludeResourcePrefixes().isEmpty()) {
          boolean matched = false;
          for (String prefix : this.options.getIncludeResourcePrefixes()) {
            String className = classDoc.qualifiedName();
            if (className.startsWith(prefix)) {
              matched = true;
              break;
            }
          }
          excludeResource = !matched;
        }

        if (excludeResource) {
          continue;
        }

        // see if deprecated
        if (this.options.isExcludeDeprecatedResourceClasses()
            && ParserHelper.isDeprecated(classDoc, this.options)) {
          continue;
        }

        // see if excluded via a tag
        if (ParserHelper.hasTag(classDoc, this.options.getExcludeClassTags())) {
          continue;
        }

        docletClasses.add(classDoc);
      }

      ClassDocCache classCache = new ClassDocCache(docletClasses);

      List<ApiDeclaration> declarations = null;

      // build up set of subresources
      // do simple parsing to find sub resource classes
      // these are ones referenced in the return types of methods
      // which have a path but no http method
      Map<Type, ClassDoc> subResourceClasses = new HashMap<Type, ClassDoc>();
      for (ClassDoc classDoc : docletClasses) {
        ClassDoc currentClassDoc = classDoc;
        while (currentClassDoc != null) {

          for (MethodDoc method : currentClassDoc.methods()) {
            // if the method has @Path but no Http method then its an entry point to a sub resource
            if (!ParserHelper.resolveMethodPath(method, this.options).isEmpty()
                && HttpMethod.fromMethod(method) == null) {
              ClassDoc subResourceClassDoc = classCache.findByType(method.returnType());
              if (subResourceClassDoc != null) {
                if (this.options.isLogDebug()) {
                  System.out.println(
                      "Adding return type as sub resource class : "
                          + subResourceClassDoc.name()
                          + " for method "
                          + method.name()
                          + " of referencing class "
                          + currentClassDoc.name());
                }
                subResourceClasses.put(method.returnType(), subResourceClassDoc);
              }
            }
          }

          currentClassDoc = currentClassDoc.superclass();

          // ignore parent object class
          if (!ParserHelper.hasAncestor(currentClassDoc)) {
            break;
          }
        }
      }

      // parse with the v2 parser that supports endpoints of the same resource being spread across
      // resource files
      Map<String, ApiDeclaration> resourceToDeclaration = new HashMap<String, ApiDeclaration>();
      for (ClassDoc classDoc : docletClasses) {
        CrossClassApiParser classParser =
            new CrossClassApiParser(
                this.options,
                classDoc,
                docletClasses,
                subResourceClasses,
                typeClasses,
                SWAGGER_VERSION,
                this.options.getApiVersion(),
                this.options.getApiBasePath());
        classParser.parse(resourceToDeclaration);
      }
      Collection<ApiDeclaration> declarationColl = resourceToDeclaration.values();

      if (this.options.isLogDebug()) {
        System.out.println("After parse phase api declarations are: ");
        for (ApiDeclaration apiDec : declarationColl) {
          System.out.println(
              "Api Dec: base path "
                  + apiDec.getBasePath()
                  + ", res path: "
                  + apiDec.getResourcePath());
          for (Api api : apiDec.getApis()) {
            System.out.println("Api path:" + api.getPath());
            for (Operation op : api.getOperations()) {
              System.out.println("Api nick name:" + op.getNickname() + " method " + op.getMethod());
            }
          }
        }
      }

      // add any extra declarations
      if (this.options.getExtraApiDeclarations() != null
          && !this.options.getExtraApiDeclarations().isEmpty()) {
        declarationColl = new ArrayList<ApiDeclaration>(declarationColl);
        declarationColl.addAll(this.options.getExtraApiDeclarations());
      }

      // set root path on any empty resources
      for (ApiDeclaration api : declarationColl) {
        if (api.getResourcePath() == null
            || api.getResourcePath().isEmpty()
            || api.getResourcePath().equals("/")) {
          api.setResourcePath(this.options.getResourceRootPath());
        }
      }

      // merge the api declarations
      declarationColl =
          new ApiDeclarationMerger(
                  SWAGGER_VERSION, this.options.getApiVersion(), this.options.getApiBasePath())
              .merge(declarationColl);

      // clear any empty models
      for (ApiDeclaration api : declarationColl) {
        if (api.getModels() != null && api.getModels().isEmpty()) {
          api.setModels(null);
        }
      }

      declarations = new ArrayList<ApiDeclaration>(declarationColl);

      // sort the api declarations if needed
      if (this.options.isSortResourcesByPriority()) {

        Collections.sort(
            declarations,
            new Comparator<ApiDeclaration>() {

              public int compare(ApiDeclaration dec1, ApiDeclaration dec2) {
                return Integer.compare(dec1.getPriority(), dec2.getPriority());
              }
            });

      } else if (this.options.isSortResourcesByPath()) {
        Collections.sort(
            declarations,
            new Comparator<ApiDeclaration>() {

              public int compare(ApiDeclaration dec1, ApiDeclaration dec2) {
                if (dec1 == null || dec1.getResourcePath() == null) {
                  return 1;
                }
                if (dec2 == null || dec2.getResourcePath() == null) {
                  return -1;
                }
                return dec1.getResourcePath().compareTo(dec2.getResourcePath());
              }
            });
      }

      // sort apis of each declaration
      if (this.options.isSortApisByPath()) {
        for (ApiDeclaration dec : declarations) {
          if (dec.getApis() != null) {
            Collections.sort(
                dec.getApis(),
                new Comparator<Api>() {

                  public int compare(Api o1, Api o2) {
                    if (o1 == null || o1.getPath() == null) {
                      return -1;
                    }
                    return o1.getPath().compareTo(o2.getPath());
                  }
                });
          }
        }
      }

      writeApis(declarations);
      // Copy swagger-ui into the output directory.
      if (this.options.isIncludeSwaggerUi()) {
        copyUi();
      }
      return true;
    } catch (IOException e) {
      System.err.println("Failed to write api docs, err msg: " + e.getMessage());
      e.printStackTrace();
      return false;
    }
  }