| /** |
| * 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); |
| } |
| } |
| } |