/*
* Copyright (C) 2010 eXo Platform SAS.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.wikbook.core.codesource;
import org.wikbook.codesource.CodeSource;
import org.wikbook.codesource.CodeSourceBuilder;
import org.wikbook.codesource.CodeSourceBuilderContext;
import org.wikbook.codesource.SignedMemberSource;
import org.wikbook.codesource.TypeSource;
import org.wikbook.core.Utils;
import org.wikbook.text.TextArea;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
* @version $Revision$
*/
public class CodeProcessor
{
/** . */
public static final String WHITE_NON_CR = "[ \t\\x0B\f\r]";
/** . */
public static final Pattern CALLOUT_ANCHOR_PATTERN = Pattern.compile(
"//" + WHITE_NON_CR + "*" + "<([0-9]*)>" + "(.*)$", Pattern.MULTILINE);
/** . */
public static final Pattern CALLOUT_DEF_PATTERN = Pattern.compile(
"^" + WHITE_NON_CR + "*//" + WHITE_NON_CR + "*" + "=([0-9]+)=" + WHITE_NON_CR + "(\\S.*)$", Pattern.MULTILINE);
/** . */
public static final Pattern BEGIN_CHUNK_PATTERN = Pattern.compile(
"^" + WHITE_NON_CR + "*//" + WHITE_NON_CR + "*" + "-([0-9]+)-" + WHITE_NON_CR + "*" + "$");
/** . */
public static final Pattern BLANK_LINE_PATTERN = Pattern.compile(
"^" + WHITE_NON_CR + "*" + "$");
/** . */
public static final Pattern JAVA_INCLUDE_PATTERN = Pattern.compile(
"\\{" + "@(include|javadoc)" + "\\s+" + "([^\\s]+)" + "\\s*" + "(\\{[0-9]+(?:,[0-9]+)*\\})?" + "\\s*" + "\\}"
);
/** The last index found. */
int calloutIndex = 0;
/** The index sub increments. */
int calloutSubIndex = 0;
/**
* Updates the current callout index with the provided id. When the calloutId argument is an empty string, the last
* found index is incremented. When the calloutId is an integer string, the last callout index is updated.
*
* @param calloutId the callout id.
* @return the effective id value to use
*/
private String updateId(String calloutId)
{
if (calloutId.length() == 0)
{
return "" + (calloutIndex * 1000 + calloutSubIndex++);
}
else
{
calloutIndex = Integer.parseInt(calloutId);
calloutSubIndex = 1;
return "" + calloutIndex * 1000;
}
}
/**
* Convert the calloutId argument as an effective callout.
*
* @param calloutId the calloutId
* @return the effective id value to use
*/
private String getId(String calloutId)
{
if (calloutId.length() == 0)
{
throw new AssertionError();
}
else
{
int index = Integer.parseInt(calloutId);
return "" + index * 1000;
}
}
private void printJavaLine(String s, CodeContext ctx)
{
// Process all callout definitions
Matcher coDefMatcher = CALLOUT_DEF_PATTERN.matcher(s);
int pre = 0;
StringBuilder buf = new StringBuilder();
while (coDefMatcher.find())
{
String calloutId = coDefMatcher.group(1);
String calloutText = coDefMatcher.group(2).trim();
//
String id = getId(calloutId);
//
buf.append(s, pre, coDefMatcher.start());
//
ctx.setCallout(id, calloutText);
//
pre = coDefMatcher.end();
}
buf.append(s, pre, s.length());
s = buf.toString();
//
TextArea ta = new TextArea(s);
Matcher matcher = CALLOUT_ANCHOR_PATTERN.matcher(s);
int prev = 0;
while (matcher.find())
{
String calloutId = matcher.group(1);
//
String id = updateId(calloutId);
//
ctx.writeContent(ta.clip(ta.position(prev), ta.position(matcher.start())));
//
ctx.writeCallout(id);
// Determine if we have callout text associated
String text = matcher.group(2);
if (!text.matches("\\s*"))
{
ctx.setCallout(id, text.trim());
}
// Iterate to next
prev = matcher.end();
}
ctx.writeContent(ta.clip(ta.position(prev)));
}
private void printJavaSource(
SignedMemberSource methodSource,
CodeContext ctx,
Set<String> chunkIds)
{
String source = methodSource.getClip();
// Find the method curly braces
int from = source.indexOf('{');
int to = source.lastIndexOf('}');
source = source.substring(from + 1, to);
// Split lines
printJavaSource(source, ctx, chunkIds);
}
private void printJavaSource(
String source,
CodeContext ctx)
{
// Split lines
for (Iterator<String> i = Utils.split(source).iterator(); i.hasNext();)
{
String line = i.next();
Matcher matcher = BEGIN_CHUNK_PATTERN.matcher(line);
if (matcher.matches())
{
// Remove line
}
else
{
if (i.hasNext())
{
printJavaLine(line + "\n", ctx);
}
else
{
printJavaLine(line, ctx);
}
}
}
}
private void printJavaSource(
String source,
CodeContext ctx,
Set<String> chunkIds)
{
// Split lines
boolean matches = false;
for (String line : Utils.split(source))
{
if (BLANK_LINE_PATTERN.matcher(line).matches())
{
matches = false;
}
else
{
Matcher matcher = BEGIN_CHUNK_PATTERN.matcher(line);
if (matcher.matches())
{
String chunkId = matcher.group(1);
matches = chunkIds == null || chunkIds.contains(chunkId);
}
else
{
if (matches)
{
printJavaLine(line + "\n", ctx);
}
}
}
}
}
public void parse(String s, final CodeContext ctx)
{
int prev = 0;
Matcher matcher = JAVA_INCLUDE_PATTERN.matcher(s);
while (matcher.find())
{
JavaCodeLink l = JavaCodeLink.parse(matcher.group(2));
CodeSourceBuilder builder = new CodeSourceBuilder(new CodeSourceBuilderContext()
{
public InputStream getResource(String path)
{
try
{
return ctx.resolveResources(path);
}
catch (IOException e)
{
e.printStackTrace();
}
return null;
}
});
//
printJavaSource(s.substring(prev, matcher.start()), ctx);
TypeSource typeSource = builder.buildClass(l.getFQN());
if (typeSource == null)
{
throw new RuntimeException("Could not resolve type " + l.getFQN());
}
CodeSource source;
if (l.getMember() != null)
{
source = typeSource.findMember(l.getMember());
}
else
{
source = typeSource;
}
//
if (source != null)
{
if ("include".equals(matcher.group(1)))
{
if (matcher.group(3) != null)
{
String subset = matcher.group(3);
String a = subset.substring(1, subset.length() - 1);
String[] ids = a.split(",");
printJavaSource((SignedMemberSource)source, ctx, new HashSet<String>(Arrays.asList(ids)));
}
else
{
printJavaSource(source.getClip(), ctx);
}
}
else if ("javadoc".equals(matcher.group(1)) && source.getJavaDoc() != null)
{
String javadoc = source.getJavaDoc();
ctx.writeContent(javadoc);
}
}
else
{
ctx.writeContent("Could not locate the " + l + " source");
}
//
prev = matcher.end();
}
//
printJavaSource(s.substring(prev), ctx);
}
}