This is an automated email from the ASF dual-hosted git repository.

bdelacretaz pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-repoinit-parser.git


The following commit(s) were added to refs/heads/master by this push:
     new 94c95ab  SLING-10236 : Introduce Operation.asRepoInitString (#9)
94c95ab is described below

commit 94c95ab7939064176045d7bcc1f582c7d212f34b
Author: anchela <[email protected]>
AuthorDate: Tue Mar 30 15:13:36 2021 +0200

    SLING-10236 : Introduce Operation.asRepoInitString (#9)
    
    * SLING-10236 : Introduce Operation.asRepoInitString (initial draft with 
FIXMEs and TODOs)
    
    * SLING-10236 : Introduce Operation.asRepoInitString (rename escape method, 
add additional assertion in test assuming that Operation.toString is suitable 
to compare expected and generated result)
    
    * SLING-10236 : Introduce Operation.asRepoInitString (wip)
    
    * SLING-10236 : Introduce Operation.asRepoInitString (missing notnull 
annotations)
    
    * SLING-10236 : Introduce Operation.asRepoInitString (verbose output for 
repoinitparseexception in tests)
    
    * SLING-10236 : Introduce Operation.asRepoInitString (incorporate 
SLING-10192, thanks Karl for spotting)
    
    * SLING-10236 : Introduce Operation.asRepoInitString (move AsRepoInitTest 
such that Operation.getParameterDescription is accessible to the test)
    
    * SLING-10236 - clarify the test's purpose
    
    * SLING-10236 - refactor AsRepoInitTest to reuse the ParserTest logic
    
    * SLING-10236 - use Action.toString()
    
    Co-authored-by: angela <[email protected]>
    Co-authored-by: Bertrand Delacretaz <[email protected]>
---
 pom.xml                                            |  6 ++
 .../repoinit/parser/operations/AclGroupBase.java   | 61 +++++++++++++++++++
 .../parser/operations/AddGroupMembers.java         |  7 +++
 .../repoinit/parser/operations/CreateGroup.java    |  7 +++
 .../repoinit/parser/operations/CreatePath.java     | 27 ++++++++-
 .../parser/operations/CreateServiceUser.java       |  7 +++
 .../repoinit/parser/operations/CreateUser.java     | 23 +++++++
 .../repoinit/parser/operations/DeleteGroup.java    |  7 +++
 .../parser/operations/DeleteServiceUser.java       |  7 +++
 .../repoinit/parser/operations/DeleteUser.java     |  7 +++
 .../parser/operations/DisableServiceUser.java      |  9 ++-
 .../repoinit/parser/operations/Operation.java      | 41 ++++++++++++-
 .../operations/OperationWithPathOptions.java       | 12 ++++
 .../parser/operations/PathSegmentDefinition.java   | 24 ++++++--
 .../parser/operations/RegisterNamespace.java       |  7 +++
 .../parser/operations/RegisterNodetypes.java       | 41 ++++++++++++-
 .../parser/operations/RegisterPrivilege.java       |  7 +++
 .../parser/operations/RemoveGroupMembers.java      |  7 +++
 .../repoinit/parser/operations/SetAclPaths.java    | 12 +++-
 .../parser/operations/SetAclPrincipalBased.java    | 10 +++-
 .../parser/operations/SetAclPrincipals.java        | 18 +++++-
 .../repoinit/parser/operations/SetProperties.java  | 44 ++++++++++++++
 .../repoinit/parser/operations/AsRepoInitTest.java | 70 ++++++++++++++++++++++
 .../sling/repoinit/parser/test/ParserTest.java     | 63 ++++++++++---------
 24 files changed, 484 insertions(+), 40 deletions(-)

diff --git a/pom.xml b/pom.xml
index 0d78786..465267c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -105,6 +105,12 @@
           <version>2.20.0</version>
           <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>org.jetbrains</groupId>
+            <artifactId>annotations</artifactId>
+            <version>18.0.0</version>
+            <scope>provided</scope>
+        </dependency>
 
         <!-- testing -->
         <dependency>
diff --git 
a/src/main/java/org/apache/sling/repoinit/parser/operations/AclGroupBase.java 
b/src/main/java/org/apache/sling/repoinit/parser/operations/AclGroupBase.java
index 9bc5861..12e02a3 100644
--- 
a/src/main/java/org/apache/sling/repoinit/parser/operations/AclGroupBase.java
+++ 
b/src/main/java/org/apache/sling/repoinit/parser/operations/AclGroupBase.java
@@ -20,8 +20,11 @@ package org.apache.sling.repoinit.parser.operations;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Formatter;
 import java.util.List;
 
+import org.apache.sling.repoinit.parser.operations.AclLine.Action;
+import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.versioning.ProviderType;
 
 /** Base class for operations that group AclLines */
@@ -59,4 +62,62 @@ abstract class AclGroupBase extends Operation {
     public List<String> getOptions() {
         return aclOptions;
     }
+
+    String asRepoInit(@NotNull String topLine, boolean hasPathLines) {
+        try (Formatter formatter = new Formatter()) {
+            formatter.format("%s",topLine);
+            for (AclLine line : lines) {
+                String action = actionToString(line.getAction());
+                String privileges = privilegesToString(line.getAction(), 
line.getProperty(AclLine.PROP_PRIVILEGES));
+                String onOrFor;
+                if (hasPathLines) {
+                    String pathStr = 
pathsToString(line.getProperty(AclLine.PROP_PATHS));
+                    onOrFor = (pathStr.isEmpty()) ? "" : " on " + pathStr;
+                } else {
+                    onOrFor = " for " + 
listToString(line.getProperty(AclLine.PROP_PRINCIPALS));
+                }
+                formatter.format("    %s %s%s%s%s%n", action, privileges, 
onOrFor,
+                        
nodetypesToString(line.getProperty(AclLine.PROP_NODETYPES)),
+                        restrictionsToString(line.getRestrictions()));
+            }
+            formatter.format("end%n");
+            return formatter.toString();
+        }
+    }
+
+    @NotNull
+    String getAclOptionsString() {
+        return (aclOptions.isEmpty()) ? "" : " (ACLOptions="+ 
listToString(aclOptions)+")";
+    }
+
+    @NotNull
+    static String privilegesToString(@NotNull AclLine.Action action, @NotNull 
List<String> privileges) {
+        return (action == AclLine.Action.REMOVE_ALL) ? "*" : 
listToString(privileges);
+    }
+
+    @NotNull
+    private static String nodetypesToString(@NotNull List<String> nodetypes) {
+        return (nodetypes.isEmpty()) ? "" : " nodetypes " + 
listToString(nodetypes);
+    }
+
+    @NotNull
+    private static String restrictionsToString(@NotNull 
List<RestrictionClause> restrictionClauses) {
+        StringBuilder sb = new StringBuilder();
+        for (RestrictionClause rc : restrictionClauses) {
+            sb.append(" restriction(").append(rc.getName());
+            for (String v : rc.getValues()) {
+                sb.append(",").append(v);
+            }
+            sb.append(')');
+        }
+        return sb.toString();
+    }
+
+    @NotNull
+    private static String actionToString(@NotNull AclLine.Action action) {
+        if(action.equals(Action.REMOVE_ALL)) {
+            return Action.REMOVE.toString().toLowerCase();
+        }
+        return action.toString().toLowerCase();
+    }
 }
diff --git 
a/src/main/java/org/apache/sling/repoinit/parser/operations/AddGroupMembers.java
 
b/src/main/java/org/apache/sling/repoinit/parser/operations/AddGroupMembers.java
index 89aa5ac..5ccb49f 100644
--- 
a/src/main/java/org/apache/sling/repoinit/parser/operations/AddGroupMembers.java
+++ 
b/src/main/java/org/apache/sling/repoinit/parser/operations/AddGroupMembers.java
@@ -19,6 +19,7 @@ package org.apache.sling.repoinit.parser.operations;
 
 import java.util.List;
 
+import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.versioning.ProviderType;
 
 @ProviderType
@@ -51,6 +52,12 @@ public class AddGroupMembers extends Operation {
         return sb.toString();
     }
 
+    @NotNull
+    @Override
+    public String asRepoInitString() {
+        return String.format("add %s to group %s%n", listToString(members), 
groupname);
+    }
+
     public String getGroupname() {
         return groupname;
     }
diff --git 
a/src/main/java/org/apache/sling/repoinit/parser/operations/CreateGroup.java 
b/src/main/java/org/apache/sling/repoinit/parser/operations/CreateGroup.java
index 34a594b..3384c46 100644
--- a/src/main/java/org/apache/sling/repoinit/parser/operations/CreateGroup.java
+++ b/src/main/java/org/apache/sling/repoinit/parser/operations/CreateGroup.java
@@ -18,6 +18,7 @@
 package org.apache.sling.repoinit.parser.operations;
 
 import org.apache.sling.repoinit.parser.impl.WithPathOptions;
+import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.versioning.ProviderType;
 
 @ProviderType
@@ -59,6 +60,12 @@ public class CreateGroup extends OperationWithPathOptions {
         return sb.toString();
     }
 
+    @NotNull
+    @Override
+    public String asRepoInitString() {
+        return asRepoInitString("group", groupname);
+    }
+
     public String getGroupname() {
         return groupname;
     }
diff --git 
a/src/main/java/org/apache/sling/repoinit/parser/operations/CreatePath.java 
b/src/main/java/org/apache/sling/repoinit/parser/operations/CreatePath.java
index 4b66144..0a68ef9 100644
--- a/src/main/java/org/apache/sling/repoinit/parser/operations/CreatePath.java
+++ b/src/main/java/org/apache/sling/repoinit/parser/operations/CreatePath.java
@@ -21,6 +21,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
+import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.versioning.ProviderType;
 
 @ProviderType
@@ -43,6 +44,28 @@ public class CreatePath extends Operation {
         return pathDef.toString();
     }
 
+    @NotNull
+    @Override
+    public String asRepoInitString() {
+        String defaultTypeStr = (defaultPrimaryType == null) ? "" : 
"("+defaultPrimaryType+") ";
+        StringBuilder sb = new StringBuilder();
+        for (PathSegmentDefinition psd : getDefinitions()) {
+            sb.append("/").append(psd.getSegment());
+            List<String> mixins = psd.getMixins();
+            if (!psd.isDefaultPrimary() || !mixins.isEmpty()) {
+                sb.append("(");
+                if (!psd.isDefaultPrimary()) {
+                    sb.append(psd.getPrimaryType());
+                }
+                if (!mixins.isEmpty()) {
+                    sb.append(" mixin ").append(listToString(mixins));
+                }
+                sb.append(")");
+            }
+        }
+        return String.format("create path %s%s%n", defaultTypeStr, 
sb.toString());
+    }
+
     @Override
     public void accept(OperationVisitor v) {
         v.visitCreatePath(this);
@@ -62,16 +85,18 @@ public class CreatePath extends Operation {
                 continue;
             }
             String pt = defaultPrimaryType;
+            boolean isDefaultPrimary = true;
             List<String> ms = null;
             if(i == segments.length -1) {
                 if (primaryType != null) {
                     pt = primaryType;
+                    isDefaultPrimary = false;
                 }
                 if (mixins != null && ! mixins.isEmpty()) {
                     ms = mixins;
                 }
             }
-            pathDef.add(new PathSegmentDefinition(segments[i], pt, ms));
+            pathDef.add(new PathSegmentDefinition(segments[i], pt, ms, 
isDefaultPrimary));
         }
     }
     
diff --git 
a/src/main/java/org/apache/sling/repoinit/parser/operations/CreateServiceUser.java
 
b/src/main/java/org/apache/sling/repoinit/parser/operations/CreateServiceUser.java
index e6b7611..ade4d12 100644
--- 
a/src/main/java/org/apache/sling/repoinit/parser/operations/CreateServiceUser.java
+++ 
b/src/main/java/org/apache/sling/repoinit/parser/operations/CreateServiceUser.java
@@ -18,6 +18,7 @@
 package org.apache.sling.repoinit.parser.operations;
 
 import org.apache.sling.repoinit.parser.impl.WithPathOptions;
+import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.versioning.ProviderType;
 
 @ProviderType
@@ -48,4 +49,10 @@ public class CreateServiceUser extends ServiceUserOperation {
 
         return sb.toString();
     }
+
+    @NotNull
+    @Override
+    public String asRepoInitString() {
+        return asRepoInitString("service user", username);
+    }
 }
diff --git 
a/src/main/java/org/apache/sling/repoinit/parser/operations/CreateUser.java 
b/src/main/java/org/apache/sling/repoinit/parser/operations/CreateUser.java
index 7d18b1b..827cdf5 100644
--- a/src/main/java/org/apache/sling/repoinit/parser/operations/CreateUser.java
+++ b/src/main/java/org/apache/sling/repoinit/parser/operations/CreateUser.java
@@ -18,6 +18,7 @@
 package org.apache.sling.repoinit.parser.operations;
 
 import org.apache.sling.repoinit.parser.impl.WithPathOptions;
+import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.versioning.ProviderType;
 
 @ProviderType
@@ -70,6 +71,28 @@ public class CreateUser extends OperationWithPathOptions {
         return sb.toString();
     }
 
+    @NotNull
+    @Override
+    public String asRepoInitString() {
+        String path = getPath();
+        if (path == null || path.isEmpty()) {
+            return String.format("create user %s%s%n", username, 
getPwString());
+        } else {
+            String forced = (isForcedPath()) ? "forced " : "";
+            return String.format("create user %s with %spath %s%s%n", 
username, forced, path, getPwString());
+        }
+    }
+
+    @NotNull
+    private String getPwString() {
+        if (password == null || password.isEmpty()) {
+            return "";
+        } else {
+            String enc = (passwordEncoding != null) ? "{"+passwordEncoding+"} 
" :  "";
+            return String.format(" with password %s%s", enc, password);
+        }
+    }
+
     public String getUsername() {
         return username;
     }
diff --git 
a/src/main/java/org/apache/sling/repoinit/parser/operations/DeleteGroup.java 
b/src/main/java/org/apache/sling/repoinit/parser/operations/DeleteGroup.java
index 0d10651..1fcb807 100644
--- a/src/main/java/org/apache/sling/repoinit/parser/operations/DeleteGroup.java
+++ b/src/main/java/org/apache/sling/repoinit/parser/operations/DeleteGroup.java
@@ -17,6 +17,7 @@
 
 package org.apache.sling.repoinit.parser.operations;
 
+import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.versioning.ProviderType;
 
 @ProviderType
@@ -42,6 +43,12 @@ public class DeleteGroup extends Operation {
         return groupname;
     }
 
+    @NotNull
+    @Override
+    public String asRepoInitString() {
+        return String.format("delete group %s%n", groupname);
+    }
+
     public String getGroupname() {
         return groupname;
     }
diff --git 
a/src/main/java/org/apache/sling/repoinit/parser/operations/DeleteServiceUser.java
 
b/src/main/java/org/apache/sling/repoinit/parser/operations/DeleteServiceUser.java
index 91d0061..359653d 100644
--- 
a/src/main/java/org/apache/sling/repoinit/parser/operations/DeleteServiceUser.java
+++ 
b/src/main/java/org/apache/sling/repoinit/parser/operations/DeleteServiceUser.java
@@ -17,6 +17,7 @@
 
 package org.apache.sling.repoinit.parser.operations;
 
+import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.versioning.ProviderType;
 
 @ProviderType
@@ -29,4 +30,10 @@ public class DeleteServiceUser extends ServiceUserOperation {
     public void accept(OperationVisitor v) {
         v.visitDeleteServiceUser(this);
     }
+
+    @NotNull
+    @Override
+    public String asRepoInitString() {
+        return String.format("delete service user %s%n", username);
+    }
 }
diff --git 
a/src/main/java/org/apache/sling/repoinit/parser/operations/DeleteUser.java 
b/src/main/java/org/apache/sling/repoinit/parser/operations/DeleteUser.java
index f0934d0..7f85d18 100644
--- a/src/main/java/org/apache/sling/repoinit/parser/operations/DeleteUser.java
+++ b/src/main/java/org/apache/sling/repoinit/parser/operations/DeleteUser.java
@@ -17,6 +17,7 @@
 
 package org.apache.sling.repoinit.parser.operations;
 
+import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.versioning.ProviderType;
 
 @ProviderType
@@ -40,6 +41,12 @@ public class DeleteUser extends Operation {
         return username;
     }
 
+    @NotNull
+    @Override
+    public String asRepoInitString() {
+        return String.format("delete user %s%n", username);
+    }
+
     public String getUsername() {
         return username;
     }
diff --git 
a/src/main/java/org/apache/sling/repoinit/parser/operations/DisableServiceUser.java
 
b/src/main/java/org/apache/sling/repoinit/parser/operations/DisableServiceUser.java
index 6e4a393..433a041 100644
--- 
a/src/main/java/org/apache/sling/repoinit/parser/operations/DisableServiceUser.java
+++ 
b/src/main/java/org/apache/sling/repoinit/parser/operations/DisableServiceUser.java
@@ -17,6 +17,7 @@
 
 package org.apache.sling.repoinit.parser.operations;
 
+import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.versioning.ProviderType;
 
 @ProviderType
@@ -41,7 +42,13 @@ public class DisableServiceUser extends ServiceUserOperation 
{
         }
         return sb.toString();
     }
-    
+
+    @NotNull
+    @Override
+    public String asRepoInitString() {
+        return String.format("disable service user %s : %s%n", username, 
escapeQuotes(reason));
+    }
+
     @Override
     public void accept(OperationVisitor v) {
         v.visitDisableServiceUser(this);
diff --git 
a/src/main/java/org/apache/sling/repoinit/parser/operations/Operation.java 
b/src/main/java/org/apache/sling/repoinit/parser/operations/Operation.java
index bf6581e..e1b1d80 100644
--- a/src/main/java/org/apache/sling/repoinit/parser/operations/Operation.java
+++ b/src/main/java/org/apache/sling/repoinit/parser/operations/Operation.java
@@ -17,15 +17,22 @@
 
 package org.apache.sling.repoinit.parser.operations;
 
+import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.versioning.ProviderType;
 
+import java.util.List;
+import java.util.stream.Collectors;
+
 @ProviderType
 public abstract class Operation {
     public abstract void accept(OperationVisitor v);
     public static final String DQUOTE = "\"";
     
     protected abstract String getParametersDescription();
-    
+
+    @NotNull
+    public abstract String asRepoInitString();
+
     @Override
     public String toString() {
         return getClass().getSimpleName() + " " + getParametersDescription();
@@ -41,4 +48,36 @@ public abstract class Operation {
         }
         return s;
     }
+
+    @NotNull
+    static String escapeQuotes(@NotNull String s) {
+        String esc = s.replace("\\", "\\\\");
+        String escapequotes = esc.replace("\"", "\\\"");
+        return "\"" + escapequotes + "\"";
+    }
+
+    @NotNull
+    static String listToString(@NotNull List<String> list) {
+        if (list.isEmpty()) {
+            return "";
+        } else {
+            return String.join(",", list);
+        }
+    }
+
+    @NotNull
+    static String pathsToString(@NotNull List<String> paths) {
+        return listToString(paths.stream()
+                .map(s -> {
+                    if (s.startsWith(":") && s.contains("#")) {
+                        String func = s.substring(1, s.indexOf(":",1));
+                        String s2 = s.substring(func.length()+2, 
s.lastIndexOf('#'));
+                        String trailingPath = (s.endsWith("#")) ?  "" : 
s.substring(s.indexOf("#")+1);
+                        return func + "(" + s2 +")" + trailingPath;
+                    } else {
+                        return s;
+                    }
+                })
+                .collect(Collectors.toList()));
+    }
 }
diff --git 
a/src/main/java/org/apache/sling/repoinit/parser/operations/OperationWithPathOptions.java
 
b/src/main/java/org/apache/sling/repoinit/parser/operations/OperationWithPathOptions.java
index 79c00b8..a4f1f65 100644
--- 
a/src/main/java/org/apache/sling/repoinit/parser/operations/OperationWithPathOptions.java
+++ 
b/src/main/java/org/apache/sling/repoinit/parser/operations/OperationWithPathOptions.java
@@ -18,6 +18,7 @@
 package org.apache.sling.repoinit.parser.operations;
 
 import org.apache.sling.repoinit.parser.impl.WithPathOptions;
+import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.versioning.ProviderType;
 
 @ProviderType
@@ -35,4 +36,15 @@ abstract class OperationWithPathOptions extends Operation {
     public boolean isForcedPath() {
         return wpopt.forcedPath;
     }
+
+    @NotNull
+    String asRepoInitString(@NotNull String type, @NotNull String name) {
+        String path = wpopt.path;
+        if (path == null || path.isEmpty()) {
+            return String.format("create %s %s%n", type, name);
+        } else {
+            String forced = (wpopt.forcedPath) ? "forced " : "";
+            return String.format("create %s %s with %spath %s%n", type, name, 
forced, path);
+        }
+    }
 }
diff --git 
a/src/main/java/org/apache/sling/repoinit/parser/operations/PathSegmentDefinition.java
 
b/src/main/java/org/apache/sling/repoinit/parser/operations/PathSegmentDefinition.java
index 96ce656..327156a 100644
--- 
a/src/main/java/org/apache/sling/repoinit/parser/operations/PathSegmentDefinition.java
+++ 
b/src/main/java/org/apache/sling/repoinit/parser/operations/PathSegmentDefinition.java
@@ -17,8 +17,10 @@
 
 package org.apache.sling.repoinit.parser.operations;
 
+import java.util.Collections;
 import java.util.List;
 
+import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.versioning.ProviderType;
 
 /** Defines a segment of a path to be created,
@@ -29,22 +31,32 @@ public class PathSegmentDefinition {
     private final String segment;
     private final String primaryType;
     private final List<String> mixins;
-    
+    private final boolean isDefaultPrimary;
+
     public PathSegmentDefinition(String segment, String primaryType) {
-        this(segment, primaryType, null);
+        this(segment, primaryType, null, false);
     }
 
     public PathSegmentDefinition(String segment, String primaryType, 
List<String> mixins) {
+        this(segment, primaryType, mixins, false);
+    }
+
+    public PathSegmentDefinition(String segment, String primaryType, boolean 
isDefaultPrimary) {
+        this(segment, primaryType, null, isDefaultPrimary);
+    }
+
+    public PathSegmentDefinition(String segment, String primaryType, 
List<String> mixins, boolean isDefaultPrimary) {
         this.segment = segment;
         this.primaryType = primaryType;
-        this.mixins = mixins;
+        this.mixins = (mixins == null) ? Collections.emptyList() : mixins;
+        this.isDefaultPrimary = isDefaultPrimary;
     }
 
     public String toString() {
         final StringBuilder sb = new StringBuilder();
         sb.append(segment);
         boolean hasPrimaryType = primaryType != null;
-        boolean hasMixin = mixins != null && ! mixins.isEmpty();
+        boolean hasMixin = !mixins.isEmpty();
         if (hasPrimaryType || hasMixin) {
             sb.append("(");
             if (hasPrimaryType) {
@@ -73,4 +85,8 @@ public class PathSegmentDefinition {
     public List<String> getMixins() {
         return mixins;
     }
+
+    public boolean isDefaultPrimary() {
+        return isDefaultPrimary;
+    }
 }
diff --git 
a/src/main/java/org/apache/sling/repoinit/parser/operations/RegisterNamespace.java
 
b/src/main/java/org/apache/sling/repoinit/parser/operations/RegisterNamespace.java
index 5d8f0dc..9ba2c99 100644
--- 
a/src/main/java/org/apache/sling/repoinit/parser/operations/RegisterNamespace.java
+++ 
b/src/main/java/org/apache/sling/repoinit/parser/operations/RegisterNamespace.java
@@ -17,6 +17,7 @@
 
 package org.apache.sling.repoinit.parser.operations;
 
+import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.versioning.ProviderType;
 
 @ProviderType
@@ -42,6 +43,12 @@ public class RegisterNamespace extends Operation {
         return sb.toString();
     }
 
+    @NotNull
+    @Override
+    public String asRepoInitString() {
+        return String.format("register namespace ( %s ) %s%n", prefix, uri);
+    }
+
     @Override
     public void accept(OperationVisitor v) {
         v.visitRegisterNamespace(this);
diff --git 
a/src/main/java/org/apache/sling/repoinit/parser/operations/RegisterNodetypes.java
 
b/src/main/java/org/apache/sling/repoinit/parser/operations/RegisterNodetypes.java
index fa43c5e..2a34489 100644
--- 
a/src/main/java/org/apache/sling/repoinit/parser/operations/RegisterNodetypes.java
+++ 
b/src/main/java/org/apache/sling/repoinit/parser/operations/RegisterNodetypes.java
@@ -17,8 +17,16 @@
 
 package org.apache.sling.repoinit.parser.operations;
 
+import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.versioning.ProviderType;
 
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Formatter;
+import java.util.List;
+
 /** An embedded block of text */
 @ProviderType
 public class RegisterNodetypes extends Operation {
@@ -46,7 +54,38 @@ public class RegisterNodetypes extends Operation {
         sb.append(getCndStatements());
         return sb.toString();
     }
-    
+
+    @NotNull
+    @Override
+    public String asRepoInitString() {
+        try (Formatter formatter = new Formatter()) {
+            for (String nodetypeRegistrationSentence : 
generateRepoInitLines(new BufferedReader(new StringReader(cndStatements)))) {
+                formatter.format("%s%n", nodetypeRegistrationSentence);
+            }
+            return formatter.toString();
+        } catch (IOException e) {
+            throw new RuntimeException("Unexpected IOException", e);
+        }
+    }
+
+    @NotNull
+    public static List<String> generateRepoInitLines(@NotNull BufferedReader 
rawLines) throws IOException {
+        List<String> lines = new ArrayList<>();
+        lines.add("register nodetypes");
+        lines.add("<<===");
+
+        String raw;
+        while((raw = rawLines.readLine()) != null) {
+            if (raw.isEmpty()) {
+                lines.add("");
+            } else {
+                lines.add("<< "+raw);
+            }
+        }
+        lines.add("===>>");
+        return lines;
+    }
+
     @Override
     public void accept(OperationVisitor v) {
         v.visitRegisterNodetypes(this);
diff --git 
a/src/main/java/org/apache/sling/repoinit/parser/operations/RegisterPrivilege.java
 
b/src/main/java/org/apache/sling/repoinit/parser/operations/RegisterPrivilege.java
index 3b62f87..c5e49f5 100644
--- 
a/src/main/java/org/apache/sling/repoinit/parser/operations/RegisterPrivilege.java
+++ 
b/src/main/java/org/apache/sling/repoinit/parser/operations/RegisterPrivilege.java
@@ -21,6 +21,7 @@ import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 
+import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.versioning.ProviderType;
 
 @ProviderType
@@ -59,6 +60,12 @@ public class RegisterPrivilege extends Operation {
         return this.privilegeName + "," + this.isAbstract + "," + 
this.declaredAggregateNames;
     }
 
+    @NotNull
+    @Override
+    public String asRepoInitString() {
+        return String.format("%s%n", toString());
+    }
+
     public String toString() {
         StringBuilder builder = new StringBuilder("register ");
         if (this.isAbstract) {
diff --git 
a/src/main/java/org/apache/sling/repoinit/parser/operations/RemoveGroupMembers.java
 
b/src/main/java/org/apache/sling/repoinit/parser/operations/RemoveGroupMembers.java
index ecd28c2..320e647 100644
--- 
a/src/main/java/org/apache/sling/repoinit/parser/operations/RemoveGroupMembers.java
+++ 
b/src/main/java/org/apache/sling/repoinit/parser/operations/RemoveGroupMembers.java
@@ -19,6 +19,7 @@ package org.apache.sling.repoinit.parser.operations;
 
 import java.util.List;
 
+import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.versioning.ProviderType;
 
 @ProviderType
@@ -51,6 +52,12 @@ public class RemoveGroupMembers extends Operation {
         return sb.toString();
     }
 
+    @NotNull
+    @Override
+    public String asRepoInitString() {
+        return String.format("remove %s from group %s%n", 
listToString(members), groupname);
+    }
+
     public String getGroupname() {
         return groupname;
     }
diff --git 
a/src/main/java/org/apache/sling/repoinit/parser/operations/SetAclPaths.java 
b/src/main/java/org/apache/sling/repoinit/parser/operations/SetAclPaths.java
index 4b60953..e43aed7 100644
--- a/src/main/java/org/apache/sling/repoinit/parser/operations/SetAclPaths.java
+++ b/src/main/java/org/apache/sling/repoinit/parser/operations/SetAclPaths.java
@@ -21,6 +21,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
+import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.versioning.ProviderType;
 
 /** Set ACL statement that groups a set of AclLines
@@ -32,7 +33,7 @@ public class SetAclPaths extends AclGroupBase {
     private final List<String> paths;
     
     public SetAclPaths(List<String> paths, List<AclLine> lines) {
-        this(paths,lines,new ArrayList<String>());
+        this(paths,lines,new ArrayList<>());
     }
 
     public SetAclPaths(List<String> paths,List<AclLine> lines, List<String> 
aclOptions){
@@ -46,7 +47,14 @@ public class SetAclPaths extends AclGroupBase {
         sb.append(super.getParametersDescription());
         return sb.toString(); 
     }
-    
+
+    @NotNull
+    @Override
+    public String asRepoInitString() {
+        String topline = String.format("set ACL on %s%s%n", 
pathsToString(paths), getAclOptionsString());
+        return asRepoInit(topline, false);
+    }
+
     public List<String> getPaths() {
         return paths;
     }
diff --git 
a/src/main/java/org/apache/sling/repoinit/parser/operations/SetAclPrincipalBased.java
 
b/src/main/java/org/apache/sling/repoinit/parser/operations/SetAclPrincipalBased.java
index 8c8b3b5..c8d8538 100644
--- 
a/src/main/java/org/apache/sling/repoinit/parser/operations/SetAclPrincipalBased.java
+++ 
b/src/main/java/org/apache/sling/repoinit/parser/operations/SetAclPrincipalBased.java
@@ -21,6 +21,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
+import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.versioning.ProviderType;
 
 /** Set ACL statement that groups a set of AclLines
@@ -46,7 +47,14 @@ public class SetAclPrincipalBased extends AclGroupBase {
         sb.append(super.getParametersDescription());
         return sb.toString(); 
     }
-    
+
+    @NotNull
+    @Override
+    public String asRepoInitString() {
+        String topline = String.format("set principal ACL for %s%s%n", 
listToString(principals), getAclOptionsString());
+        return asRepoInit(topline, true);
+    }
+
     public List<String> getPrincipals() {
         return principals;
     }
diff --git 
a/src/main/java/org/apache/sling/repoinit/parser/operations/SetAclPrincipals.java
 
b/src/main/java/org/apache/sling/repoinit/parser/operations/SetAclPrincipals.java
index d740cc4..2c8c1b1 100644
--- 
a/src/main/java/org/apache/sling/repoinit/parser/operations/SetAclPrincipals.java
+++ 
b/src/main/java/org/apache/sling/repoinit/parser/operations/SetAclPrincipals.java
@@ -21,6 +21,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
+import org.jetbrains.annotations.NotNull;
 import org.osgi.annotation.versioning.ProviderType;
 
 /** Set ACL statement that groups a set of AclLines
@@ -46,7 +47,22 @@ public class SetAclPrincipals extends AclGroupBase {
         sb.append(super.getParametersDescription());
         return sb.toString(); 
     }
-    
+
+    @NotNull
+    @Override
+    public String asRepoInitString() {
+        if (getLines().stream().anyMatch(line -> {
+            List<String> paths = line.getProperty(AclLine.PROP_PATHS);
+            return paths == null || paths.isEmpty();
+        })) {
+            String topline = String.format("set repository ACL for %s%s%n", 
listToString(principals), getAclOptionsString());
+            return asRepoInit(topline, true);
+        } else {
+            String topline = String.format("set ACL for %s%s%n", 
listToString(principals), getAclOptionsString());
+            return asRepoInit(topline, true);
+        }
+    }
+
     public List<String> getPrincipals() {
         return principals;
     }
diff --git 
a/src/main/java/org/apache/sling/repoinit/parser/operations/SetProperties.java 
b/src/main/java/org/apache/sling/repoinit/parser/operations/SetProperties.java
index f72ca11..48a70ad 100644
--- 
a/src/main/java/org/apache/sling/repoinit/parser/operations/SetProperties.java
+++ 
b/src/main/java/org/apache/sling/repoinit/parser/operations/SetProperties.java
@@ -18,8 +18,15 @@
 package org.apache.sling.repoinit.parser.operations;
 
 
+import java.util.Calendar;
+import java.util.Formatter;
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
+import org.apache.jackrabbit.util.ISO8601;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 import org.osgi.annotation.versioning.ProviderType;
 
 @ProviderType
@@ -46,6 +53,43 @@ public class SetProperties extends Operation {
         return sb.toString();
     }
 
+    @NotNull
+    @Override
+    public String asRepoInitString() {
+        // FIXME: see SLING-10238 for type and quoted values that cannot be 
generated
+        //        exactly as they were originally defined in repo-init
+        try (Formatter formatter = new Formatter()) {
+            formatter.format("set properties on %s%n", pathsToString(paths));
+            for (PropertyLine line : lines) {
+                String type = (line.getPropertyType() == null) ? "" : "{" + 
line.getPropertyType().name() + "}";
+                String values = valuesToString(line.getPropertyValues(), 
line.getPropertyType());
+                if (line.isDefault()) {
+                    formatter.format("default %s%s to %s%n", 
line.getPropertyName(), type, values);
+                } else {
+                    formatter.format("set %s%s to %s%n", 
line.getPropertyName(), type, values);
+                }
+            }
+            formatter.format("end%n");
+            return formatter.toString();
+        }
+    }
+
+    @NotNull
+    private static String valuesToString(@NotNull List<Object> values, 
@Nullable PropertyLine.PropertyType type) {
+        List<String> strings = values.stream()
+                .map(o -> {
+                    if (type == null || type == 
PropertyLine.PropertyType.String) {
+                        return escapeQuotes(Objects.toString(o, ""));
+                    } else if (type == PropertyLine.PropertyType.Date) {
+                        return "\"" + ISO8601.format((Calendar) o) + "\"";
+                    } else {
+                        return Objects.toString(o, null);
+                    }
+                })
+                .collect(Collectors.toList());
+        return listToString(strings);
+    }
+
     public List<String> getPaths() {
         return paths;
     }
diff --git 
a/src/test/java/org/apache/sling/repoinit/parser/operations/AsRepoInitTest.java 
b/src/test/java/org/apache/sling/repoinit/parser/operations/AsRepoInitTest.java
new file mode 100644
index 0000000..01c2d52
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/repoinit/parser/operations/AsRepoInitTest.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.sling.repoinit.parser.operations;
+
+import org.apache.sling.repoinit.parser.RepoInitParsingException;
+import org.apache.sling.repoinit.parser.impl.RepoInitParserService;
+import org.apache.sling.repoinit.parser.test.ParserTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/** Similar to {@link ParserTest} but uses {@link 
Operation#asRepoInitString()})
+ *  to rebuild the input script after parsing it, to verify that that operation
+ *  returns equivalent statements.
+ */
+@RunWith(Parameterized.class)
+public class AsRepoInitTest {
+
+    private final ParserTest.TestCase tc;
+
+    @Parameters(name="{0}")
+    public static Collection<Object[]> data() throws IOException {
+        return ParserTest.TestCase.buildTestData();
+    }
+
+    public AsRepoInitTest(ParserTest.TestCase tc) {
+        this.tc = tc;
+    }
+
+    /** Rebuild the input script using {@link Operation#asRepoInitString()}) */
+    private static Reader rebuildInputScript(Reader input) throws Exception {
+        StringBuilder sb = new StringBuilder();
+        for (Operation o : new RepoInitParserService().parse(input)) {
+            sb.append(o.asRepoInitString());
+        }
+        return new StringReader(sb.toString());
+    }
+
+    @Test
+    public void checkResultAsRepoInit() throws Exception {
+        try {
+            ParserTest.TestCase.validate(rebuildInputScript(tc.input), 
tc.expected);
+        } finally {
+            tc.close();
+        }
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/repoinit/parser/test/ParserTest.java 
b/src/test/java/org/apache/sling/repoinit/parser/test/ParserTest.java
index d51dd6d..3195c58 100644
--- a/src/test/java/org/apache/sling/repoinit/parser/test/ParserTest.java
+++ b/src/test/java/org/apache/sling/repoinit/parser/test/ParserTest.java
@@ -49,10 +49,10 @@ public class ParserTest {
 
     public static final String DEFAULT_ENCODING = "UTF-8";
 
-    static class TestCase {
-        final Reader input;
+    public static class TestCase {
+        public final Reader input;
         final String inputFilename;
-        final InputStream expected;
+        public final InputStream expected;
         final String outputFilename;
 
         private static final String PREFIX = "/testcases/test-";
@@ -74,7 +74,7 @@ public class ParserTest {
             expected = getClass().getResourceAsStream(outputFilename);
         }
 
-        static TestCase build(int index) throws IOException {
+        public static TestCase build(int index) throws IOException {
             final TestCase result = new TestCase(index);
             if(result.input == null || result.expected == null) {
                 return null;
@@ -82,7 +82,7 @@ public class ParserTest {
             return result;
         }
 
-        void close() {
+        public void close() {
             try {
                 input.close();
             } catch(IOException ignored) {
@@ -92,21 +92,41 @@ public class ParserTest {
             } catch(IOException ignored) {
             }
         }
+
+        public static void validate(Reader validateInput, InputStream 
validateExpected) throws RepoInitParsingException, IOException {
+            final String expected = IOUtils.toString(validateExpected, 
DEFAULT_ENCODING).trim();
+            final StringWriter sw = new StringWriter();
+            final OperationVisitor v = new OperationToStringVisitor(new 
PrintWriter(sw));
+            final List<Operation> result = new 
RepoInitParserService().parse(validateInput);
+            for(Operation o : result) {
+                o.accept(v);
+            }
+            sw.flush();
+            String actual = sw.toString().trim();
+
+            // normalize line endings to ensure tests run on windows as well
+            actual = actual.replaceAll("\r\n", "\n");
+
+            assertEquals(expected, actual);
+        }
+
+        public static Collection<Object[]> buildTestData() throws IOException {
+            final List<Object []> result = new ArrayList<>();
+            for(int i=0; i < 100; i++) {
+                final ParserTest.TestCase tc = ParserTest.TestCase.build(i);
+                if(tc != null) {
+                    result.add(new Object[] { tc });
+                }
+            }
+            return result;
+        }
     }
 
     private final TestCase tc;
 
     @Parameters(name="{0}")
     public static Collection<Object[]> data() throws IOException {
-        final List<Object []> result = new ArrayList<>();
-        for(int i=0; i < 100; i++) {
-            final TestCase tc = TestCase.build(i);
-            if(tc != null) {
-                result.add(new Object[] { tc });
-            }
-        }
-        return result;
-
+        return TestCase.buildTestData();
     }
 
     public ParserTest(TestCase tc) {
@@ -115,21 +135,8 @@ public class ParserTest {
 
     @Test
     public void checkResult() throws RepoInitParsingException, IOException {
-        final String expected = IOUtils.toString(tc.expected, 
DEFAULT_ENCODING).trim();
         try {
-            final StringWriter sw = new StringWriter();
-            final OperationVisitor v = new OperationToStringVisitor(new 
PrintWriter(sw));
-            final List<Operation> result = new 
RepoInitParserService().parse(tc.input);
-            for(Operation o : result) {
-                o.accept(v);
-            }
-            sw.flush();
-            String actual = sw.toString().trim();
-
-            // normalize line endings to ensure tests run on windows as well
-            actual = actual.replaceAll("\r\n", "\n");
-
-            assertEquals(expected, actual);
+            TestCase.validate(tc.input, tc.expected);
         } finally {
             tc.close();
         }

Reply via email to