Importing WformServices and GlemmServices

Change-Id: Ifa95576d69e0d3863f63d3fdedb48c2c21cf64bc
diff --git a/JsonTraverse/.classpath b/JsonTraverse/.classpath
new file mode 100644
index 0000000..1ff5a38
--- /dev/null
+++ b/JsonTraverse/.classpath
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/classes" path="src">
+		<attributes>
+			<attribute name="optional" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+			<attribute name="org.eclipse.jst.component.nondependency" value=""/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.jst.server.core.container/org.eclipse.jst.server.tomcat.runtimeTarget/Apache Tomcat v9.0">
+		<attributes>
+			<attribute name="owner.project.facets" value="jst.utility"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jdk1.8.0_231">
+		<attributes>
+			<attribute name="owner.project.facets" value="java"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>
diff --git a/JsonTraverse/.project b/JsonTraverse/.project
new file mode 100644
index 0000000..2313174
--- /dev/null
+++ b/JsonTraverse/.project
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>JsonTraverse</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.wst.common.project.facet.core.builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.wst.validation.validationbuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.m2e.core.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
+		<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
+		<nature>org.eclipse.m2e.core.maven2Nature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
+	</natures>
+</projectDescription>
diff --git a/JsonTraverse/.settings/org.eclipse.jdt.core.prefs b/JsonTraverse/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..4e4a3ad
--- /dev/null
+++ b/JsonTraverse/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,9 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.release=disabled
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/JsonTraverse/.settings/org.eclipse.m2e.core.prefs b/JsonTraverse/.settings/org.eclipse.m2e.core.prefs
new file mode 100644
index 0000000..f897a7f
--- /dev/null
+++ b/JsonTraverse/.settings/org.eclipse.m2e.core.prefs
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
diff --git a/JsonTraverse/.settings/org.eclipse.wst.common.component b/JsonTraverse/.settings/org.eclipse.wst.common.component
new file mode 100644
index 0000000..049ec8b
--- /dev/null
+++ b/JsonTraverse/.settings/org.eclipse.wst.common.component
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?><project-modules id="moduleCoreId" project-version="1.5.0">
+    <wb-module deploy-name="JsonTraverse">
+        <wb-resource deploy-path="/" source-path="/src"/>
+    </wb-module>
+</project-modules>
diff --git a/JsonTraverse/.settings/org.eclipse.wst.common.project.facet.core.xml b/JsonTraverse/.settings/org.eclipse.wst.common.project.facet.core.xml
new file mode 100644
index 0000000..3eca3e1
--- /dev/null
+++ b/JsonTraverse/.settings/org.eclipse.wst.common.project.facet.core.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<faceted-project>
+  <runtime name="Apache Tomcat v9.0"/>
+  <installed facet="java" version="1.8"/>
+  <installed facet="jst.utility" version="1.0"/>
+</faceted-project>
diff --git a/JsonTraverse/.settings/org.eclipse.wst.validation.prefs b/JsonTraverse/.settings/org.eclipse.wst.validation.prefs
new file mode 100644
index 0000000..04cad8c
--- /dev/null
+++ b/JsonTraverse/.settings/org.eclipse.wst.validation.prefs
@@ -0,0 +1,2 @@
+disabled=06target
+eclipse.preferences.version=1
diff --git a/JsonTraverse/pom.xml b/JsonTraverse/pom.xml
new file mode 100644
index 0000000..caf90bf
--- /dev/null
+++ b/JsonTraverse/pom.xml
@@ -0,0 +1,44 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>de.korap.json</groupId>
+  <artifactId>JsonTraverse</artifactId>
+  <version>0.1-SNAPSHOT</version>
+  <packaging>jar</packaging>
+  <name>JsonTraverse</name>
+  <description>Methods for traversing a Json Tree (KorallQuery) and extracting/rewriting data</description>
+ 
+  <dependencies>
+   <dependency>
+    <groupId>com.fasterxml.jackson.core</groupId>
+    <artifactId>jackson-core</artifactId>
+    <version>2.9.6</version>
+    <scope>compile</scope>
+   </dependency>
+	<dependency>
+	  <groupId>com.fasterxml.jackson.core</groupId>
+	  <artifactId>jackson-annotations</artifactId>
+	  <version>2.9.6</version>
+	</dependency>
+	<dependency>
+	  <groupId>com.fasterxml.jackson.core</groupId>
+	  <artifactId>jackson-databind</artifactId>
+	  <version>2.9.6</version>
+	  <scope>provided</scope>
+	</dependency>
+  </dependencies>
+ 
+ 
+  <build>
+    <sourceDirectory>src</sourceDirectory>
+    <plugins>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.7.0</version>
+        <configuration>
+          <source>1.8</source>
+          <target>1.8</target>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
\ No newline at end of file
diff --git a/JsonTraverse/src/de/korap/json/JsonTraverse.java b/JsonTraverse/src/de/korap/json/JsonTraverse.java
new file mode 100644
index 0000000..60e87fa
--- /dev/null
+++ b/JsonTraverse/src/de/korap/json/JsonTraverse.java
@@ -0,0 +1,158 @@
+package de.korap.json;
+
+import java.io.PrintStream;
+import java.util.Iterator;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+
+public class JsonTraverse
+
+{
+	// kind of operations during JsonTree Traversal:
+	
+	public final static int COLLECT 	= 1; 
+	public final static int REWRITE 	= 2;	// replace lemmata by refs on expansion list.
+	public final static int REWRITE_EXP	= 3;	// replace lemmata by expansion list [wf,wf,...].
+	
+	PrintStream
+		fout,			// to be set by caller, replaces stdout.
+		ferr;			// to be set by caller, replaces stderr.
+	
+	/*
+	 * traverseJsonTree
+	 * 
+	 * - traverses JsonTree starting at node.
+	 * - tPath: collects lemmata and contains references to rewrite.
+	 * - mode: operation mode COLLECT, REWRITE, REWRITE_EXP.
+	 * 31.03.20/FB
+	 */
+	
+	static final public void traverseJsonTree(
+				JsonNode 			node, 
+				TraversedPath	 	tPath, 
+				final int	 		mode, 
+				final PrintStream	fout,
+				final PrintStream	ferr )
+	
+	{
+	final String func = "traverseJsonTree";
+	boolean
+		isFound = false;
+	boolean
+		bDebug = false;
+	
+    if( node.isObject())
+    	{
+        Iterator<String> fieldNames = node.fieldNames();
+        if( bDebug )
+        	fout.printf("Debug:%s: object: { \n",  func);
+        
+        while(fieldNames.hasNext()) 
+        	{
+            String fieldName = fieldNames.next();
+            
+            // path search under 'query':
+            if( tPath.checkPath(fieldName, node.get(fieldName), mode, fout) )
+            	isFound = true;
+            
+            JsonNode fieldValue = node.get(fieldName);
+            if( bDebug )
+            	fout.printf("Debug:%s: key='%s':\n", func, fieldName); 
+            traverseJsonTree(fieldValue, tPath, mode, fout, ferr);
+        	}
+        
+        if( bDebug )
+        	fout.printf("Debug:%s: } : end of object.\n",  func);
+        
+        if( (mode == REWRITE || mode == REWRITE_EXP) && isFound )
+        	{
+        	tPath.rewriteObjNode(node, mode, fout);
+        	fout.printf("Debug:%s: after rewrite: node='%s'.\n", func, node.toString());
+        	}
+    	} 
+    else if(node.isArray())
+    	{
+    	if( bDebug )
+    		fout.printf("Debug:%s: array: [\n",  func);
+    	
+        ArrayNode arrayNode = (ArrayNode) node;
+        for(int i = 0; i < arrayNode.size(); i++) 
+        	{
+            JsonNode arrayElement = arrayNode.get(i);
+            traverseJsonTree(arrayElement, tPath, mode, fout, ferr);
+        	}
+    	if( bDebug ) 
+    		fout.printf("Debug:%s: ] end of array.\n",  func);
+    	} 
+    else {
+        // JsonNode node represents a single value field - do something with it.
+    	if( bDebug )
+    		fout.printf("Debug:%s: single value field = '%s'\n", func, node.asText());
+    	}
+    
+	} // traverseJsonTree
+	
+	/*
+	 * isPathLemmaQuery
+	 * 
+	 * returns: lemma as a String, if path & lemma were found.
+	 * 			else: null.
+	 * 04.04.20/FB
+	 */
+	
+	static private String isPathLemmaQuery(JsonNode node, PrintStream fout, PrintStream ferr)
+	
+	{
+	final String func = "isPathLemmaQuery";
+	int
+		nMatches = 0;
+	String
+		lemma = null;
+	boolean
+		bDebug = false;
+	
+	if( node.isObject() == false )
+		return null;
+	
+	Iterator<String> fieldNames = node.fieldNames();
+    
+    while(fieldNames.hasNext()) 
+    	{
+        String fieldName = fieldNames.next();
+        String fieldValue = node.get(fieldName).asText();
+        
+        if( fieldName.equals("@type") && fieldValue.equals("koral:token") )
+        	nMatches++;
+        else if( fieldName.equals("wrap") && node.get(fieldName).isObject() == true ) 
+	        {
+        	Iterator<String>fieldNames2 = node.get(fieldName).fieldNames();
+        	while( fieldNames2.hasNext())
+	        	{
+        		String fn = fieldNames2.next();
+        		if( fn.equals("@type") && node.get(fn).asText().equals("koral:term") )
+        			nMatches++;
+        		else if( fn.equals("key") )
+	        		{
+	        		lemma = node.get(fn).asText();	
+		        	}
+           		else if( fn.equals("layer") && node.get(fn).asText().equals("lemma") )
+	        		nMatches++;
+	        	} // while
+	        }
+    	} // while
+	
+    if( bDebug )
+    	fout.printf("Debug: %s: nMatches=%d.\n", func, nMatches);
+    
+    if( nMatches == 3 && lemma != null )
+	    {
+	    fout.printf("Debug:%s: path found to lemma '%s'.\n", func, lemma);
+	    return lemma;
+	    }
+	
+    // path of lemma not found:
+	return null;
+	} // isPathLemmaQuery
+	
+} // JsonTraverse
diff --git a/JsonTraverse/src/de/korap/json/TraversedPath.java b/JsonTraverse/src/de/korap/json/TraversedPath.java
new file mode 100644
index 0000000..65dc242
--- /dev/null
+++ b/JsonTraverse/src/de/korap/json/TraversedPath.java
@@ -0,0 +1,426 @@
+package de.korap.json;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import de.korap.services.LemmaResponse;
+
+public class TraversedPath {
+	
+	boolean
+		insideQuery;
+	int
+		level;
+	boolean
+		okType; 
+	int
+		queryType; 			// LEMMA, etc.
+	List<String>
+		lemmata 	= null;	// the extracted lemmata.
+	String
+		lemma 		= null;	// currently found lemma.
+	List<String>
+		refs		= null;	// references to expansion lists returned from GLEMM.
+							// same order than lemmata in lemmata[].
+	JsonNode
+		wrapNode	= null; // node under 'wrap' where properties of lemma term 
+							// are stored, here called level 2.
+	ObjectMapper
+		objMapper	= null;
+	int
+		iRewrite	= 0;	// index of lemma currently rewritten (in lemmata).
+	List<List<String>>
+		expansionLists	= null;	// each element of this list is an expansion list
+								// i.e. a list of wfs for a lemma.
+	List<LemmaResponse>			// list of lemmaResponse corresponding to each
+		lemRespList		= null;	// lemma of list lemmata.
+								// todo: should replace List expansionLists.
+	
+	public static final int NONE		= 0; // undefined.
+	public static final int LEMMA		= 1; // GLEMM lemma.
+	public static final int REGEXPR		= 2; // COSMAS II's regex on word level.
+	public static final int WILDCARD	= 3; // COSMAS II's wildcard expression.
+	
+	public TraversedPath(ObjectMapper obj)
+	
+		{
+		objMapper	= obj;
+		insideQuery = false;
+		okType 		= false;
+		level 		= 0;
+		queryType	= NONE;
+		}
+	
+	/*
+	 * checkPath
+	 * 
+	 * notes:
+	 * - to be called for each property of an object.
+	 * - collects all lemma subqueries inside a query tree.
+	 * Arguments:
+	 * fieldName: currently processed field name.
+	 * node		: JsonNode that is the value of fieldName.
+	 * mode		: COLLECT or REWRITE.
+	 * Returns:
+	 * - List<String>lemmata as a list of lemmata.
+	 * - true if wrap-object to be rewritten has been found.
+	 * 04.04.20/FB
+	 */
+	
+	boolean checkPath(final String fieldName, JsonNode node, final int mode, final PrintStream fout)
+	
+	{
+	final String func = "checkPath";
+	final boolean
+		bLog 	= true; 	// for loging.
+	boolean
+		isFound = false;	// true, when wrap-obj to be rewritten has been found.
+	
+	if( insideQuery == false )
+		{
+		if( fieldName.equals("query") )
+			{
+			insideQuery = true;
+			level = 1;
+			okType = false;
+			}
+		return false;
+		}
+	
+	if( level == 1 )
+		{
+		if( !okType && fieldName.equals("@type") )
+			{
+			if( node.isTextual() && node.asText().equals("koral:token") )
+				{
+				okType = true;
+				}
+			return false;
+			}
+		else if( okType && fieldName.equals("wrap") )
+			{
+			level 		= 2;
+			okType 		= false; // reset for next level.
+			queryType	= NONE;
+			lemma 		= null;
+			wrapNode	= node;
+			if( bLog && fout != null )
+				fout.printf("Debug: %s: wrap-Obj: '%s'\n", func, node.toString());
+			}
+		return false;
+		} // level 1
+	
+	if( level == 2 )
+		{
+		if( fieldName.equals("@type") )
+			{
+			if( node.isTextual() && node.asText().equals("koral:term") )
+				okType = true;
+			else
+				{ // reset, this is not the requested @type.
+				level = 1;
+				okType = false;
+				}
+			}
+		else if( fieldName.equals("layer") ) 
+			{
+			if( node.isTextual() && node.asText().equals("lemma") )
+				queryType = LEMMA;
+			}
+		else if( fieldName.equals("key") )
+			{
+			if( node.isTextual() )
+				lemma = node.asText();
+			}
+		
+		if( okType && queryType == LEMMA && lemma != null )
+			{
+			if( mode == JsonTraverse.COLLECT )
+				{
+				if( lemmata == null )
+					lemmata = new ArrayList<String>();
+				lemmata.add(lemma);
+				
+				if( bLog && fout != null )
+					fout.printf("Debug: %s: found: lemma='%s' as koral:term.\n", func, lemma);
+				}
+			
+			isFound = true; 
+			
+			// reset before searching the next lemma (i.e. @type); supposing we stay underneath node 'query'.
+			level  = 1; 
+			okType = false;
+			}
+		return isFound;
+		} // level 2
+	
+	return false;
+	} // checkPath
+
+	/* rewriteObjNode
+	 * notes: https://mkyong.com/java/jackson-tree-model-example/
+	 * 08.04.20/FB
+	 */
+	
+	public void rewriteObjNode(JsonNode node, final int mode, final PrintStream fout)
+	
+	{
+	final String func = "rewriteObjNode";
+	
+	if( mode == JsonTraverse.REWRITE )
+		{
+		fout.printf("Debug: %s: rewriting for lemma '%s' ('%s') > '%s'...\n", func, 
+					lemma, lemmata.get(iRewrite), refs.get(iRewrite));
+		
+		((ObjectNode)node).put("key",   "");
+		((ObjectNode)node).put("layer", "orth");
+		((ObjectNode)node).put("desc",  "&"+lemma);
+		((ObjectNode)node).put("hide",  "key"); // TODO: must be ["key"] !
+		((ObjectNode)node).put("ref",   refs.get(iRewrite)); // TODO: must be ["key"] !
+		iRewrite++;
+		}
+	else // mode == REWRITE_EXP
+		{
+		fout.printf("Debug: %s: rewriting for lemma '%s'.\n", func, lemmata.get(iRewrite)); 
+	
+		// inserting expansion list from corresponding Lemma Response obj:
+		ArrayNode
+			array = objMapper.createArrayNode();
+		LemmaResponse
+			lemResp = lemRespList.get(iRewrite);
+		
+		for(int i=0; i<lemResp.listofWfs.size(); i++)
+			array.add(lemResp.listofWfs.get(i));
+		
+		((ObjectNode)node).set("key",   array);	// expansion list as Json Node.
+		((ObjectNode)node).put("layer", "orth");
+		((ObjectNode)node).put("desc",  "&"+lemma);
+		((ObjectNode)node).put("hide",  "key"); // TODO: must be ["key"] !
+		iRewrite++;
+		
+		/* old version :
+		ArrayNode
+			array = objMapper.createArrayNode();
+		List<String>
+			curList = expansionLists.get(iRewrite);
+		
+		for(int i=0; i<curList.size(); i++)
+			array.add(curList.get(i));
+			
+		((ObjectNode)node).set("key",   array);	// expansion list.
+		((ObjectNode)node).put("layer", "orth");
+		((ObjectNode)node).put("desc",  "&"+lemma);
+		((ObjectNode)node).put("hide",  "key"); // TODO: must be ["key"] !
+		iRewrite++;
+		*/
+		}
+
+	} // rewriteObjNode
+	
+	/*
+	 * getLemmata
+	 * 
+	 * returns list of lemmata collected and found in KoralQuery in String format.
+	 * 
+	 * 06.04.20/FB
+	 */
+	
+	public String getLemmata()
+	
+	{
+	return this.lemmata != null ? this.lemmata.toString() : "";
+	} // getLemmata
+	
+	/* getLemmataCount
+	 * 
+	 * returns no. of lemmata found in KoralQuery.
+	 * 07.04.20/FB
+	 */
+	
+	public int getLemmaCount()
+	
+	{
+	return this.lemmata != null ? this.lemmata.size() : 0;	
+	} // getLemmataCount
+	
+	/* getLemma:
+	 * returns lemma by its index i.
+	 * returns null if index out of range.
+	 * 07.04.20/FB
+	 */
+	
+	public String getLemma(int i)
+	
+	{
+	if( i < 0 || i > this.lemmata.size() )
+		return null;
+	
+	return this.lemmata.get(i);
+	} // getLemma
+	
+	/* setRef:
+	 * - inserts a reference to a instanciation list into the
+	 *   list refs by index i.
+	 * - index i must correspond to lemma i in list lemmata.
+	 * - this method ensures that list refs is allocated with as much positions
+	 *   than the size of list lemmata.
+	 * 07.04.20/FB
+	 */
+	 
+	public void setRef(String ref)
+	
+	{
+	if( this.refs == null )
+		{
+		int size = this.lemmata.size();
+		this.refs = new ArrayList<String>(size);
+		}
+	
+	this.refs.add(ref);
+	} // setRef
+	
+	/* getRefsCount:
+	 * returns no. of references stored in list this.refs.
+	 * 07.04.20/FB
+	 */
+	
+	public int getRefsCount()
+	
+	{
+	return this.refs != null ? this.refs.size() : 0 ;	
+	} // getRefsCount.
+	
+	/* getRefsasString:
+	 * - returns a printable list of references together with the corresponding
+	 *   lemmata.
+	 * 07.04.20/FB
+	 */
+	
+	public String getRefsasString()
+	
+	{
+	int
+		i, max; 
+	
+	if( this.lemmata == null || this.refs == null )
+		return "";
+	
+	//max = this.lemmata.size() > this.refs.size() ? 
+	//			this.lemmata.size() : this.refs.size();
+
+	return this.refs.toString();
+	} // getRefsasString
+	
+	/* addExpList
+	 * 
+	 * adds an Expansion List for lemma i.
+	 * Notes:
+	 * - add expList in same order as lemmata stored in list lemmata.
+	 * - todo: will be replaced by addLemmaResponse().
+	 * 08.04.20/FB
+	 */
+	
+	public void addExpList(List expList)
+	
+	{
+	if( expansionLists == null )
+		{
+		expansionLists = new ArrayList<List<String>>(lemmata.size());
+		}
+	
+	expansionLists.add(expList);
+	} // addExpList
+	
+	/*
+	 * addLemmaResponse:
+	 * - adds a LemmaResponse object for the correspondig lemma.
+	 * 30.04.20/FB
+	 * 
+	 */
+	
+	public void addLemmaResponse(LemmaResponse lemResp)
+	
+	{
+	if( this.lemRespList == null )
+		this.lemRespList = new ArrayList<LemmaResponse>(lemmata.size());
+	
+	this.lemRespList.add(lemResp);
+	} // addLemmaResponse
+	
+	/* rewriteSubQuery
+	 * 
+	 * Rules: Old			> 	New:
+	 * 		  "key": "&Haus"	"key": "ref", or
+	 * 		  "key": "&Haus"	"key": [Wf1, Wf2, ...].
+	 * 		  "layer": "lemma"	"layer": "orth"
+	 * 		  ""				"desc": "&Lemma" (neu)
+	 * 		  ""				"hide": "key"    (neu)
+	 * Arguments:
+	 * node		: expected to be a "wrap"-object.
+	 * 07.04.20/FB
+	 */
+	
+	protected void rewriteSubQuery(JsonNode node, final String lemma, final PrintStream fout)
+	
+	{
+	final String func = "rewriteSubQuery";
+	final boolean
+		bLog = true;
+	
+	if( bLog && fout != null)
+		fout.printf("Debug: %s: rewrite '%s'.\n", func, node.toString());
+	
+	if( node.isObject() )
+		{
+	    Iterator<String> 
+	    	fieldNames = node.fieldNames();
+	    
+		while(fieldNames.hasNext()) 
+			{
+		    String 
+		    	fieldName 	= fieldNames.next();
+		    JsonNode 
+		    	fieldValue = node.get(fieldName);
+		    
+		    if( fieldValue.isTextual() == false )
+		    	continue;
+		    
+		    // path search under 'query':
+		    if( fieldName.equals("key") )
+			    {
+			    if( bLog && fout != null )
+			    	fout.printf("Debug: %s: replacing %s='%s' by %s='%s'.\n", 
+				    			func, fieldName, fieldValue.asText(),
+				    			fieldName, "[...]");
+
+		    	//JsonNode newNode = objMapper.createObjectNode();
+			    //((ObjectNode)node).put("newkey", val);
+			    ((ObjectNode)node).put("key", "[...]");
+			    
+			    if( bLog && fout != null )
+			    	fout.printf("Debug: %s: %s='%s' (after replacement).\n", func, fieldName, node.get("key"));
+			    }
+		    else if( fieldName.equals("layer") )
+			    {
+			    	
+			    }
+		    
+			} // while
+
+		if( bLog && fout != null )
+			fout.printf("Debug: %s: after rewriting, wrap-Obj='%s'.\n", func, node.toString());
+
+		} // object
+	
+	} // rewriteSubQuery
+	
+	
+} // class TraversedPath
+
diff --git a/JsonTraverse/src/de/korap/services/LemmaResponse.java b/JsonTraverse/src/de/korap/services/LemmaResponse.java
new file mode 100644
index 0000000..bd6fd79
--- /dev/null
+++ b/JsonTraverse/src/de/korap/services/LemmaResponse.java
@@ -0,0 +1,104 @@
+package de.korap.services;
+
+import java.util.List;
+
+import javax.xml.bind.annotation.XmlAccessorType;
+//import javax.xml.bind.annotation.XmlRootElement;
+//import javax.xml.bind.annotation.XmlType;
+import javax.xml.bind.annotation.*;
+
+/* General infos:
+ * http://blog.bdoughan.com/2011/06/using-jaxbs-xmlaccessortype-to.html
+ */
+
+/*
+ * LemmaResponse: 
+ * Object to return by GlemmServices on a Lemma Request.
+ * Will be converted by glassfish to JSON.
+ * Notes:
+ * - header fields are prexided by 'head_' because JSON will be sorted alphabetically by JAXB.
+ * 
+ * 24.01.20/FB
+ */
+
+//@XmlRootElement(name="LemmaResponse") - no effect
+@XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
+//@XmlType (name="abc", propOrder={"query", "options", "nWfs", "listofWfs"}) - no effect
+
+
+public class LemmaResponse 
+
+{
+	final int PRINT_LIST_LIMIT	= 10; // do not print more than 10 Wfs of listofWfs[].
+	
+	public int
+		head_nWfs = 0;
+	public String
+		head_query   = null,
+		head_options = null, // e.g. "+flex-sonst",
+		head_errMess = null;
+			
+	public List<String>
+		listofWfs = null;
+	
+	public LemmaResponse()
+	{
+		
+	}
+	
+	/* toString
+	 * - formats partially a LemmaResponse obj to string.
+	 * Returns formated string or null if an error occures.
+	 * 29.04.20/FB
+	 */
+	
+	public String toString()
+	
+	{
+	String
+		s = null;
+	//StringBuffer
+	//	sb = new StringBuffer();
+	
+	s = String.format("head: nWfs=%d, list=%s.", this.head_nWfs, this.listofWfs);
+	
+	return s != null ? s : "";
+	} // toString
+
+	/*   printPartialList:
+	 * 
+	 * - prints the full list of Wfs or, if the list is longer than 
+	 *   PRINT_LIST_LIMIT, only the beginning and the end of it.
+	 * 
+	 * Returns the formated (partial) list of Wfs as a string or "[]" if
+	 *         the list is empty.
+	 * 27.10.21/FB
+	 */
+	
+	public String printPartialList()
+	
+	{
+	StringBuffer
+		sb = new StringBuffer();
+	final int
+		iMin = PRINT_LIST_LIMIT/2,
+		iMax = this.listofWfs.size()-(PRINT_LIST_LIMIT/2);
+	
+	sb.append('[');
+	
+	for(int i=0; i<this.listofWfs.size(); i++)
+		{
+		if( i<iMin || i>=iMax )
+			{
+			if(i>0)
+				sb.append(',');
+			sb.append(this.listofWfs.get(i));
+			}
+		}
+	sb.append(']');
+	
+	return sb.toString();
+	
+	} // printPartialList
+
+}