blob: 6838f55a539274c18f259ed8da9bd66ca1ac1137 [file] [log] [blame]
/**
* Copyright (c) 2002-2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM - Initial API and implementation
*/
package org.eclipse.emf.codegen.jet;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import org.eclipse.emf.codegen.CodeGenPlugin;
import org.eclipse.emf.common.CommonPlugin;
import org.eclipse.emf.common.util.URI;
public class JETCompiler implements JETParseEventListener
{
protected final static char[] NULL_CHAR_ARRAY = {};
protected String[] templateURIPath;
protected String templateURI;
protected JETParser parser;
protected JETSkeleton skeleton;
protected JETReader reader;
protected PrintWriter writer;
protected List<JETGenerator> generators = new ArrayList<JETGenerator>(100);
protected List<JETConstantDataGenerator> constants = new ArrayList<JETConstantDataGenerator>(100);
protected Map<char[], JETConstantDataGenerator> constantDictionary =
new AbstractMap<char[], JETConstantDataGenerator>()
{
private final Map<String, JETConstantDataGenerator> delegate = new HashMap<String, JETConstantDataGenerator>(100, 100);
@Override
public JETConstantDataGenerator put(char[] key, JETConstantDataGenerator value)
{
return delegate.put(new String(key), value);
}
@Override
public JETConstantDataGenerator get(Object key)
{
return delegate.get(key instanceof char[] ? new String((char[])key) : key);
}
@Override
public int size()
{
return delegate.size();
}
@Override
public Set<Map.Entry<char[], JETConstantDataGenerator>> entrySet()
{
return
new AbstractSet<Map.Entry<char[], JETConstantDataGenerator>>()
{
@Override
public Iterator<Map.Entry<char[],JETConstantDataGenerator>> iterator()
{
return new Iterator<Map.Entry<char[],JETConstantDataGenerator>>()
{
private final Iterator<Map.Entry<String,JETConstantDataGenerator>> delegateIterator = delegate.entrySet().iterator();
public boolean hasNext()
{
return delegateIterator.hasNext();
}
public void remove()
{
delegateIterator.remove();
}
public Entry<char[], JETConstantDataGenerator> next()
{
final Entry<String, JETConstantDataGenerator> next = delegateIterator.next();
return
new Map.Entry<char[], JETConstantDataGenerator>()
{
public char[] getKey()
{
return next.getKey().toCharArray();
}
public JETConstantDataGenerator getValue()
{
return next.getValue();
}
public JETConstantDataGenerator setValue(JETConstantDataGenerator value)
{
return next.setValue(value);
}
@Override
public boolean equals(Object obj)
{
if (obj instanceof Map.Entry)
{
char[] key = getKey();
String keyValue = key == null ? null : new String(key);
Map.Entry<?,?> entry = (Map.Entry<?, ?>)obj;
Object otherKey = entry.getKey();
Object otherKeyValue = otherKey instanceof char[] ? new String((char[])otherKey) : null;
return
(keyValue==null ? otherKeyValue==null : keyValue.equals(otherKeyValue)) &&
(getValue()==null ? entry.getValue()==null : getValue().equals(entry.getValue()));
}
else
{
return false;
}
}
@Override
public int hashCode()
{
return next.hashCode();
}
};
}
};
}
@Override
public int size()
{
return delegate.size();
}
};
}
};
protected long constantCount = 0;
/**
* If true, the newline immediately preceding a scriptlet or directive (though not a successful include directive),
* along with any intervening spaces, will be stripped from the character data.
*/
protected boolean fNoNewLineForScriptlets = true;
protected boolean fUseStaticFinalConstants = true;
/**
* If fNoNewLineForScriptlets is true, the trailing newline/space sequence is stripped from each character
* data segment, and stored in this field. Depending on what follows, it may then be discarded or handled as its
* own character data segment.
*/
protected char[] fSavedLine = null;
/**
* The depth of the current section, where 0 is outside of any sections. A section is delimited by start and
* end directives, and must be preceded by an include directive with fail="alternative".
*/
protected int sectionDepth = 0;
/**
* Whether content is currently being skipped. This is set according to skipSections, as sections are started and ended.
*/
protected boolean skipping = false;
/**
* A stack of sections and whether to start skipping, one from each include with alternative encountered.
*/
protected Stack<SkipSection> skipSections = new Stack<SkipSection>();
/**
* A skip section entry, records the depth of the section and whether to start skipping there.
*/
static class SkipSection
{
int depth;
boolean skip;
SkipSection(int depth, boolean skip)
{
this.depth = depth;
this.skip = skip;
}
}
protected static final String CONSTANT_PREFIX = "TEXT_";
public JETCompiler(String templateURI) throws JETException
{
this(templateURI, "UTF8");
}
public JETCompiler(String templateURI, String encoding) throws JETException
{
this(templateURI, openStream(templateURI), encoding);
}
public JETCompiler(String templateURI, InputStream inputStream, String encoding) throws JETException
{
super();
this.templateURI = templateURI;
this.reader = new JETReader(templateURI, inputStream, encoding);
}
public JETCompiler(String[] templateURIPath, String relativeTemplateURI) throws JETException
{
this(templateURIPath, relativeTemplateURI, "UTF8");
}
public JETCompiler(String[] templateURIPath, String relativeTemplateURI, String encoding) throws JETException
{
super();
this.templateURIPath = templateURIPath;
this.templateURI = relativeTemplateURI;
String[] actualTemplateURI = findLocation(templateURIPath, 0, relativeTemplateURI);
this.reader = new JETReader(actualTemplateURI[1], relativeTemplateURI, openStream(actualTemplateURI[0]), encoding);
}
public String getResolvedTemplateURI()
{
return reader.getFile(0);
}
public void handleDirective(String directive, JETMark start, JETMark stop, Map<String, String> attributes) throws JETException
{
if (directive.equals("include"))
{
String fileURI = attributes.get("file");
if (fileURI != null)
{
String currentURI = start.getFile();
String[] resolvedFileURI = resolveLocation(templateURIPath, currentURI, fileURI);
if (resolvedFileURI[0].equals(currentURI))
{
boolean loop = true;
if (templateURIPath != null)
{
String baseURI = start.getBaseURI();
if (baseURI != null)
{
for (int i = 0; i < templateURIPath.length; ++i)
{
if (baseURI.equals(templateURIPath[i]))
{
resolvedFileURI = resolveLocation(templateURIPath, i + 1, currentURI, fileURI);
loop = false;
}
}
}
}
if (loop)
{
// Break the cycle.
//
return;
}
}
try
{
BufferedInputStream bufferedInputStream = new BufferedInputStream(openStream(resolvedFileURI[1]));
reader.stackStream(resolvedFileURI[2], resolvedFileURI[0], bufferedInputStream, null);
// The include succeeded, so if there is an alternative and we're not skipping, we need to start.
//
if ("alternative".equals(attributes.get("fail")))
{
skipSections.push(new SkipSection(sectionDepth + 1, !skipping));
}
// If a newline from the previous character data remains, leave it around to be processed as if it appeared in the included file.
//
if (fSavedLine != null)
{
return;
}
}
catch (JETException exception)
{
// The include failed, so if there is an alternative, we don't skip it.
//
String failType = attributes.get("fail");
if ("alternative".equals(failType))
{
skipSections.push(new SkipSection(sectionDepth + 1, false));
}
else if (!"silent".equals(failType))
{
throw
new JETException
(CodeGenPlugin.getPlugin().getString
("jet.error.file.cannot.read",
new Object [] { resolvedFileURI[1], start.format("jet.mark.file.line.column") }),
exception);
}
}
}
else
{
throw
new JETException
(CodeGenPlugin.getPlugin().getString
("jet.error.missing.attribute",
new Object []{ "file", start.format("jet.mark.file.line.column") }));
}
}
else if (directive.equals("start"))
{
sectionDepth++;
// A section is not allowed without a preceding include with alternative.
//
SkipSection skipSection = skipSections.isEmpty() ? null : (SkipSection)skipSections.peek();
if (skipSection == null || skipSection.depth != sectionDepth)
{
throw new JETException
(CodeGenPlugin.getPlugin().getString
("jet.error.section.noinclude",
new Object[] { start.format("jet.mark.file.line.column") }));
}
else if (skipSection.skip)
{
skipping = true;
}
}
else if (directive.equals("end"))
{
if (sectionDepth == 0)
{
throw
new JETException
(CodeGenPlugin.getPlugin().getString
("jet.error.unmatched.directive",
new Object[] { "start", "end", start.format("jet.mark.file.line.column") }));
}
sectionDepth--;
// This pop is safe because a section couldn't have been started without an include that pushed.
//
if (skipSections.pop().skip)
{
skipping = false;
}
}
else if (directive.equals("jet"))
{
if (skeleton != null)
{
// Multiple jet directives.
}
else
{
skeleton = new JETSkeleton();
// Process this first.
//
String skeletonURI = attributes.get("skeleton");
if (skeletonURI != null)
{
try
{
BufferedInputStream bufferedInputStream =
new BufferedInputStream(openStream(resolveLocation(templateURIPath, templateURI, skeletonURI)[1]));
byte[] input = new byte [bufferedInputStream.available()];
bufferedInputStream.read(input);
bufferedInputStream.close();
String skeletonEncoding = attributes.get("skeletonEncoding");
skeleton.setCompilationUnitContents(skeletonEncoding == null ? new String(input) : new String(input, skeletonEncoding));
}
catch (IOException exception)
{
throw new JETException(exception);
}
}
for (Map.Entry<String, String> entry : attributes.entrySet())
{
// Ignore this now
//
if (entry.getKey().equals("skeleton"))
{
// Ignore
}
else if (entry.getKey().equals("package"))
{
skeleton.setPackageName(entry.getValue());
}
else if (entry.getKey().equals("imports"))
{
skeleton.addImports(entry.getValue());
}
else if (entry.getKey().equals("class"))
{
skeleton.setClassName(entry.getValue());
}
else if (entry.getKey().equals("nlString"))
{
skeleton.setNLString(entry.getValue());
}
else if (entry.getKey().equals("startTag"))
{
parser.setStartTag(entry.getValue());
}
else if (entry.getKey().equals("endTag"))
{
parser.setEndTag(entry.getValue());
}
else if (entry.getKey().equals("version"))
{
// Ignore the version
}
else
{
throw
new JETException
(CodeGenPlugin.getPlugin().getString
("jet.error.bad.attribute",
new Object []{ entry.getKey(), start.format("jet.mark.file.line.column") }));
}
}
handleNewSkeleton();
}
}
fSavedLine = null;
}
protected void handleNewSkeleton()
{
// Do nothing
}
public void handleExpression(JETMark start, JETMark stop, Map<String, String> attributes) throws JETException
{
if (skipping) return;
JETGenerator gen = new JETExpressionGenerator(reader.getChars(start, stop));
addGenerator(gen);
}
public void handleScriptlet(JETMark start, JETMark stop, Map<String, String> attributes) throws JETException
{
if (skipping) return;
fSavedLine = null;
JETGenerator gen = new JETScriptletGenerator(reader.getChars(start, stop));
addGenerator(gen);
}
public void handleCharData(char[] chars) throws JETException
{
if (skipping) return;
if (fSavedLine != null)
{
addCharDataGenerator(fSavedLine);
fSavedLine = null;
}
if (fNoNewLineForScriptlets)
{
char[] strippedChars = stripLastNewLineWithBlanks(chars);
if (strippedChars.length > 0)
{
addCharDataGenerator(strippedChars);
}
}
else
{
addCharDataGenerator(chars);
}
}
public void addGenerator(JETGenerator gen) throws JETException
{
// If a newline from the previous character data remains, add a generator for it.
//
if (fSavedLine != null)
{
addCharDataGenerator(fSavedLine);
fSavedLine = null;
}
generators.add(gen);
}
public void addCharDataGenerator(char[] chars) throws JETException
{
// An expression with more that 931 "+" will break Sun and IBM javac compilers.
//
if (chars.length > 500)
{
int nl = 0;
int lf = 0;
int start = 0;
LOOP:
for (int i = 0; i < chars.length; ++i)
{
switch (chars[i])
{
case '\n':
{
++nl;
break;
}
case '\r':
{
++lf;
break;
}
default:
{
continue;
}
}
if (lf > 400 || nl > 400)
{
for (++i; i < chars.length; ++i)
{
switch (chars[i])
{
case '\n':
case '\r':
{
continue;
}
default:
{
int size = i - start;
char [] block = new char [size];
System.arraycopy(chars, start, block, 0, size);
doAddCharDataGenerator(block);
start = i;
nl = 0;
lf = 0;
continue LOOP;
}
}
}
}
}
if (start != 0)
{
int size = chars.length - start;
char [] block = new char [size];
System.arraycopy(chars, start, block, 0, size);
doAddCharDataGenerator(block);
return;
}
}
doAddCharDataGenerator(chars);
}
public void doAddCharDataGenerator(char[] chars) throws JETException
{
if (fUseStaticFinalConstants)
{
JETConstantDataGenerator gen = constantDictionary.get(chars);
if (gen == null)
{
if (constantCount == 0)
{
chars = stripFirstNewLineWithBlanks(chars);
}
++constantCount;
String label = CONSTANT_PREFIX + constantCount;
gen = new JETConstantDataGenerator(chars, label);
constantDictionary.put(chars, gen);
constants.add(gen);
}
generators.add(gen);
}
else
{
generators.add(new JETCharDataGenerator(chars));
}
}
protected char[] stripFirstNewLineWithBlanks(char[] chars)
{
if (chars.length >= 2 &&
(chars[0] == '\n' && chars[1] == '\r' || chars[0] == '\r' && chars[1] == '\n'))
{
chars = new String(chars, 2, chars.length - 2).toCharArray();
}
else if (chars.length >= 1 &&
(chars[0] == '\n' || chars[0] == '\r'))
{
chars = new String(chars, 1, chars.length - 1).toCharArray();
}
return chars;
}
protected char[] stripLastNewLineWithBlanks(char[] chars)
{
int i = chars.length - 1;
while (i > 0 && chars[i] == ' ')
{
--i;
}
if (chars[i] == '\n')
{
if (i > 0 && chars[i - 1] == '\r')
{
--i;
}
fSavedLine = (new String(chars, i, chars.length - i)).toCharArray();
if (i == 0)
{
return NULL_CHAR_ARRAY;
}
else
{
chars = new String(chars, 0, i).toCharArray();
return chars;
}
}
else
{
return chars;
}
}
public void beginPageProcessing()
{
// Do nothing
}
public void endPageProcessing() throws JETException
{
if (sectionDepth > 0)
{
throw
new JETException
(CodeGenPlugin.getPlugin().getString
("jet.error.unmatched.directive",
new Object[] { "end", "start", reader.mark().format("jet.mark.file.line.column") }));
}
if (skeleton == null)
{
throw
new JETException
(CodeGenPlugin.getPlugin().getString
("jet.error.missing.jet.directive",
new Object []{ reader.mark().format("jet.mark.file.line.column") }));
}
// If a newline from the previous character data remains, add a generator for it.
//
if (fSavedLine != null)
{
addCharDataGenerator(fSavedLine);
}
List<String> generatedConstants = new ArrayList<String>(constants.size());
for (JETConstantDataGenerator jetConstantDataGenerator : constants)
{
generatedConstants.add(jetConstantDataGenerator.generateConstant());
}
skeleton.setConstants(generatedConstants);
List<String> generatedBody = new ArrayList<String>(generators.size());
for (JETGenerator jetGenerator : generators)
{
generatedBody.add(jetGenerator.generate());
}
skeleton.setBody(generatedBody);
writer.print(skeleton.getCompilationUnitContents());
}
public void parse() throws JETException
{
// Register our directive.
//
JETParser.Directive directive = new JETParser.Directive();
directive.getDirectives().add("jet");
directive.getDirectives().add("include");
directive.getDirectives().add("start");
directive.getDirectives().add("end");
JETCoreElement[] coreElements =
{
directive,
new JETParser.QuoteEscape(),
new JETParser.Expression(),
new JETParser.Scriptlet()
};
Class<?> [] accept =
{
JETParser.Directive.class,
JETParser.QuoteEscape.class,
JETParser.Expression.class,
JETParser.Scriptlet.class
};
parse(coreElements, accept);
}
protected void parse(JETCoreElement[] coreElements, Class<?> [] accept) throws JETException
{
parser = new JETParser(reader, this, coreElements);
beginPageProcessing();
parser.parse(null, accept);
}
public void generate(OutputStream oStream) throws JETException
{
writer = new PrintWriter(oStream);
endPageProcessing();
writer.close();
}
public void generate(Writer writer) throws JETException
{
this.writer = new PrintWriter(writer);
endPageProcessing();
this.writer.close();
}
public JETSkeleton getSkeleton()
{
return skeleton;
}
protected static String[] resolveLocation(String[] templateURIPath, String baseLocationURI, String locationURI)
{
return resolveLocation(templateURIPath, 0, baseLocationURI, locationURI);
}
protected static String[] resolveLocation(String[] templateURIPath, int start, String baseLocationURI, String locationURI)
{
String[] result = new String []{ locationURI, locationURI, null};
URI uri = URI.createURI(locationURI);
try
{
new URL(locationURI);
uri = CommonPlugin.resolve(uri);
}
catch (MalformedURLException exception)
{
// Ignore
}
if (uri.isRelative() && uri.hasRelativePath())
{
String resolvedLocation = "";
int index = baseLocationURI.lastIndexOf("/");
if (index != -1)
{
resolvedLocation = baseLocationURI.substring(0, index + 1);
}
resolvedLocation += uri;
result[0] = resolvedLocation;
if (templateURIPath != null)
{
String [] location = findLocation(templateURIPath, start, resolvedLocation);
resolvedLocation = location[0];
result[2] = location[1];
}
if (resolvedLocation != null)
{
result[1] = resolvedLocation;
}
}
return result;
}
public static String[] findLocation(String[] locationURIPath, int start, String relativeLocationURI)
{
String[] result = { null, null};
for (int i = start; i < locationURIPath.length; ++i)
{
result[0] = locationURIPath[i];
result[1] = locationURIPath[i];
if (result[0] != null)
{
try
{
if (!result[0].endsWith("/"))
{
result[0] += "/";
}
result[0] += relativeLocationURI;
InputStream inputStream = openStream(result[0]);
inputStream.close();
break;
}
catch (JETException exception)
{
result[0] = null;
}
catch (IOException exception)
{
result[0] = null;
}
}
}
return result;
}
public static String find(String[] locationURIPath, String relativeLocationURI)
{
return findLocation(locationURIPath, 0, relativeLocationURI)[0];
}
public static InputStream openStream(String locationURI) throws JETException
{
try
{
URI uri = URI.createURI(locationURI);
URL url;
try
{
uri = CommonPlugin.resolve(uri);
url = new URL(uri.toString());
}
catch (MalformedURLException exception)
{
url = new URL("file:" + locationURI);
}
BufferedInputStream bufferedInputStream = new BufferedInputStream(url.openStream());
return bufferedInputStream;
}
catch (IOException exception)
{
throw new JETException(exception.getLocalizedMessage(), exception);
}
}
}