Factor out project validation, re-order warning and error messages.
[jSite.git] / src / de / todesbaum / jsite / application / ProjectInserter.java
index 569e2fe..c8c5e93 100644 (file)
@@ -27,14 +27,19 @@ import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 
+import de.todesbaum.jsite.application.ProjectInserter.CheckReport.Issue;
 import de.todesbaum.jsite.gui.FileScanner;
 import de.todesbaum.jsite.gui.FileScannerListener;
 import de.todesbaum.util.freenet.fcp2.Client;
@@ -380,6 +385,60 @@ public class ProjectInserter implements FileScannerListener, Runnable {
        }
 
        /**
+        * Validates the given project. The project will be checked for any invalid
+        * conditions, such as invalid insert or request keys, missing path names,
+        * missing default file, and so on.
+        *
+        * @param project
+        *            The project to check
+        * @return The encountered warnings and errors
+        */
+       public static CheckReport validateProject(Project project) {
+               CheckReport checkReport = new CheckReport();
+               if ((project.getLocalPath() == null) || (project.getLocalPath().trim().length() == 0)) {
+                       checkReport.addIssue("error.no-local-path", true);
+               }
+               if ((project.getPath() == null) || (project.getPath().trim().length() == 0)) {
+                       checkReport.addIssue("error.no-path", true);
+               }
+               if ((project.getIndexFile() == null) || (project.getIndexFile().length() == 0)) {
+                       checkReport.addIssue("warning.empty-index", false);
+               } else {
+                       File indexFile = new File(project.getLocalPath(), project.getIndexFile());
+                       if (!indexFile.exists()) {
+                               checkReport.addIssue("error.index-missing", true);
+                       }
+               }
+               String indexFile = project.getIndexFile();
+               boolean hasIndexFile = (indexFile != null);
+               if (hasIndexFile && !project.getFileOption(indexFile).getContainer().equals("")) {
+                       checkReport.addIssue("warning.container-index", false);
+               }
+               List<String> allowedIndexContentTypes = Arrays.asList("text/html", "application/xhtml+xml");
+               if (hasIndexFile && !allowedIndexContentTypes.contains(project.getFileOption(indexFile).getMimeType())) {
+                       checkReport.addIssue("warning.index-not-html", false);
+               }
+               Map<String, FileOption> fileOptions = project.getFileOptions();
+               Set<Entry<String, FileOption>> fileOptionEntries = fileOptions.entrySet();
+               boolean insert = false;
+               for (Entry<String, FileOption> fileOptionEntry : fileOptionEntries) {
+                       String fileName = fileOptionEntry.getKey();
+                       FileOption fileOption = fileOptionEntry.getValue();
+                       insert |= fileOption.isInsert() || fileOption.isInsertRedirect();
+                       if (fileName.equals(project.getIndexFile()) && !fileOption.isInsert() && !fileOption.isInsertRedirect()) {
+                               checkReport.addIssue("error.index-not-inserted", true);
+                       }
+                       if (!fileOption.isInsert() && fileOption.isInsertRedirect() && ((fileOption.getCustomKey().length() == 0) || "CHK@".equals(fileOption.getCustomKey()))) {
+                               checkReport.addIssue("error.no-custom-key", true, fileName);
+                       }
+               }
+               if (!insert) {
+                       checkReport.addIssue("error.no-files-to-insert", true);
+               }
+               return checkReport;
+       }
+
+       /**
         * {@inheritDoc}
         */
        public void run() {
@@ -499,4 +558,138 @@ public class ProjectInserter implements FileScannerListener, Runnable {
                fileScanner.removeFileScannerListener(this);
        }
 
+       /**
+        * Container class that collects all warnings and errors that occured during
+        * {@link ProjectInserter#validateProject(Project) project validation}.
+        *
+        * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+        */
+       public static class CheckReport implements Iterable<Issue> {
+
+               /** The issures that occured. */
+               private final List<Issue> issues = new ArrayList<Issue>();
+
+               /**
+                * Adds an issue.
+                *
+                * @param issue
+                *            The issue to add
+                */
+               public void addIssue(Issue issue) {
+                       issues.add(issue);
+               }
+
+               /**
+                * Creates an {@link Issue} from the given error key and fatality flag
+                * and {@link #addIssue(Issue) adds} it.
+                *
+                * @param errorKey
+                *            The error key
+                * @param fatal
+                *            {@code true} if the error is fatal, {@code false} if only
+                *            a warning should be generated
+                * @param parameters
+                *            Any additional parameters
+                */
+               public void addIssue(String errorKey, boolean fatal, String... parameters) {
+                       addIssue(new Issue(errorKey, fatal, parameters));
+               }
+
+               /**
+                * {@inheritDoc}
+                */
+               public Iterator<Issue> iterator() {
+                       return issues.iterator();
+               }
+
+               /**
+                * Returns whether this check report does not contain any errors.
+                *
+                * @return {@code true} if this check report does not contain any
+                *         errors, {@code false} if this check report does contain
+                *         errors
+                */
+               public boolean isEmpty() {
+                       return issues.isEmpty();
+               }
+
+               /**
+                * Returns the number of issues in this check report.
+                *
+                * @return The number of issues
+                */
+               public int size() {
+                       return issues.size();
+               }
+
+               /**
+                * Container class for a single issue. An issue contains an error key
+                * that describes the error, and a fatality flag that determines whether
+                * the insert has to be aborted (if the flag is {@code true}) or if it
+                * can still be performed and only a warning should be generated (if the
+                * flag is {@code false}).
+                *
+                * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’
+                *         Roden</a>
+                */
+               public static class Issue {
+
+                       /** The error key. */
+                       private final String errorKey;
+
+                       /** The fatality flag. */
+                       private final boolean fatal;
+
+                       /** Additional parameters. */
+                       private String[] parameters;
+
+                       /**
+                        * Creates a new issue.
+                        *
+                        * @param errorKey
+                        *            The error key
+                        * @param fatal
+                        *            The fatality flag
+                        * @param parameters
+                        *            Any additional parameters
+                        */
+                       protected Issue(String errorKey, boolean fatal, String... parameters) {
+                               this.errorKey = errorKey;
+                               this.fatal = fatal;
+                               this.parameters = parameters;
+                       }
+
+                       /**
+                        * Returns the key of the encountered error.
+                        *
+                        * @return The error key
+                        */
+                       public String getErrorKey() {
+                               return errorKey;
+                       }
+
+                       /**
+                        * Returns whether the issue is fatal and the insert has to be
+                        * aborted. Otherwise only a warning should be shown.
+                        *
+                        * @return {@code true} if the insert needs to be aborted, {@code
+                        *         false} otherwise
+                        */
+                       public boolean isFatal() {
+                               return fatal;
+                       }
+
+                       /**
+                        * Returns any additional parameters.
+                        *
+                        * @return The additional parameters
+                        */
+                       public String[] getParameters() {
+                               return parameters;
+                       }
+
+               }
+
+       }
+
 }