docs: public List<Project> validate(boolean validateRefs) throws IOException {
          for (Project project : projects) {
              for (ValidationEntry validationEntry : project.validationEntries) {
                  MetatypeRoot metatype = validationEntry.parsedMetatype;
                  metatype.setMetatypeFileName(validationEntry.fileName);
                  metatype.setOcdStats(ocdStats);
                  metatype.setNlsKeys(validationEntry.nlsKeys);
                  metatype.validate(validateRefs);
                  validationEntry.validity = metatype.getValidityState();
  
                  File outputFile = new File(outputPath + "/" + project.name + "/" + validationEntry.fileName + getExtension(validationEntry.fileName));
                  outputFile.getParentFile().mkdirs();
                  int pass = 0;
                  int fail = 0;
                  if (!validationEntry.localizationFound) {
                      validationEntry.validity = ValidityState.Failure;
                      fail++;
                  } else {
                      pass++;
                  }
  
                  fail += metatype.getErrorMessages().size();
                  pass += metatype.getWarningMessages().size() + metatype.getInfoMessages().size();
  
                  for (MetatypeDesignate designate : metatype.getDesignates()) {
                      fail += designate.getErrorMessages().size();
                      pass += designate.getWarningMessages().size() + designate.getInfoMessages().size();
  
                      for (MetatypeObject object : designate.getObjects()) {
                          fail += object.getErrorMessages().size();
                          pass += object.getWarningMessages().size() + object.getInfoMessages().size();
  
                          MetatypeOcd ocd = object.getMatchingOcd();
  
                          if (ocd != null) {
                              fail += ocd.getErrorMessages().size();
                              pass += ocd.getWarningMessages().size() + ocd.getInfoMessages().size();
  
                              for (MetatypeAd ad : ocd.getAds()) {
                                  fail += ad.getErrorMessages().size();
                                  pass += ad.getWarningMessages().size() + ad.getInfoMessages().size();
  
                                  for (MetatypeAdOption option : ad.getOptions()) {
                                      fail += option.getErrorMessages().size();
                                      pass += option.getWarningMessages().size() + option.getInfoMessages().size();
                                  }
                              }
                          }
                      }
  
                  }
  
                  PrintStream junit = new PrintStream(outputFile);
                  junit.printf("<testsuite errors=\"%s\" failures=\"%s\" name=\"%s\" tests=\"%s\" time=\"\">%n", "0", fail, project.name, fail + pass);
  
                  if (!validationEntry.localizationFound) {
                      junit.printf("<testcase classname=\"%s\" name=\"%s\">%n", project.name, "localizationFileNotFound");
                      junit.printf("<failure message=\"%s\"><![CDATA[%s]]></failure>%n", "Could not find file " + metatype.getLocalization(), "");
                      junit.printf("</testcase>%n");
                  }
  
                  String fileName = validationEntry.fileName;
                  processMetatype(project, metatype, fileName, junit);
  
                  for (MetatypeDesignate designate : metatype.getDesignates()) {
                      String pid = designate.getPid();
                      if (pid == null)
                          pid = designate.getFactoryPid();
                      processMetatype(project, designate, fileName + "." + pid, junit);
  
                      for (MetatypeObject object : designate.getObjects()) {
                          processMetatype(project, object, fileName + "." + pid, junit);
  
                          MetatypeOcd ocd = object.getMatchingOcd();
  
                          if (ocd != null) {
                              processMetatype(project, ocd, fileName + "." + pid, junit);
  
                              for (MetatypeAd ad : ocd.getAds()) {
                                  String id = ad.getId();
                                  processMetatype(project, ad, fileName + "." + pid + "." + id, junit);
  
                                  for (MetatypeAdOption option : ad.getOptions()) {
                                      processMetatype(project, option, fileName + "." + pid + "." + id + "." + option, junit);
                                  }
                              }
                          }
                      }
                  }
  
                  junit.printf("</testsuite>%n");
  
                  junit.close();
                  String failMsg = (fail > 0) ? "**FAILURE**" : "";
                  System.out.println("Metatype validation " + failMsg + " for " + validationEntry.fileName + " written to " + outputFile.getAbsolutePath());
              }
          }
  
          if (!errors.isEmpty()) {
              throw new IllegalStateException("xml parse errors:\n" + errors);
          }
          return projects;
      }