/* * Reference ETL Parser for Java * Copyright (c) 2000-2009 Constantine A Plotnikov * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without restriction, * including without limitation the rights to use, copy, modify, merge, * publish, distribute, sublicense, and/or sell copies of the Software, * and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package net.sf.etl.parsers.internal.term_parser.bootstrap; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.LinkedList; import java.util.List; import java.util.Stack; import java.util.logging.Level; import java.util.logging.Logger; import net.sf.etl.parsers.LiteralUtils; import net.sf.etl.parsers.PhraseParser; import net.sf.etl.parsers.PhraseTokens; import net.sf.etl.parsers.Tokens; import net.sf.etl.parsers.internal.term_parser.DefaultTermParser; import net.sf.etl.parsers.internal.term_parser.grammar.Attributes; import net.sf.etl.parsers.internal.term_parser.grammar.BlockRef; import net.sf.etl.parsers.internal.term_parser.grammar.ChoiceOp; import net.sf.etl.parsers.internal.term_parser.grammar.CompositeSyntax; import net.sf.etl.parsers.internal.term_parser.grammar.Context; import net.sf.etl.parsers.internal.term_parser.grammar.ContextImport; import net.sf.etl.parsers.internal.term_parser.grammar.ContextInclude; import net.sf.etl.parsers.internal.term_parser.grammar.ContextOp; import net.sf.etl.parsers.internal.term_parser.grammar.ContextRef; import net.sf.etl.parsers.internal.term_parser.grammar.Def; import net.sf.etl.parsers.internal.term_parser.grammar.DoclinesOp; import net.sf.etl.parsers.internal.term_parser.grammar.DocumentationSyntax; import net.sf.etl.parsers.internal.term_parser.grammar.EObject; import net.sf.etl.parsers.internal.term_parser.grammar.Element; import net.sf.etl.parsers.internal.term_parser.grammar.ExpressionRef; import net.sf.etl.parsers.internal.term_parser.grammar.ExpressionStatement; import net.sf.etl.parsers.internal.term_parser.grammar.FloatOp; import net.sf.etl.parsers.internal.term_parser.grammar.Grammar; import net.sf.etl.parsers.internal.term_parser.grammar.IdentifierOp; import net.sf.etl.parsers.internal.term_parser.grammar.IntegerOp; import net.sf.etl.parsers.internal.term_parser.grammar.KeywordStatement; import net.sf.etl.parsers.internal.term_parser.grammar.Let; import net.sf.etl.parsers.internal.term_parser.grammar.ListOp; import net.sf.etl.parsers.internal.term_parser.grammar.Modifier; import net.sf.etl.parsers.internal.term_parser.grammar.ModifierOp; import net.sf.etl.parsers.internal.term_parser.grammar.ModifiersOp; import net.sf.etl.parsers.internal.term_parser.grammar.Namespace; import net.sf.etl.parsers.internal.term_parser.grammar.NumberOp; import net.sf.etl.parsers.internal.term_parser.grammar.ObjectName; import net.sf.etl.parsers.internal.term_parser.grammar.ObjectOp; import net.sf.etl.parsers.internal.term_parser.grammar.OneOrMoreOp; import net.sf.etl.parsers.internal.term_parser.grammar.OperandOp; import net.sf.etl.parsers.internal.term_parser.grammar.OperatorDefinition; import net.sf.etl.parsers.internal.term_parser.grammar.OptionalOp; import net.sf.etl.parsers.internal.term_parser.grammar.RefOp; import net.sf.etl.parsers.internal.term_parser.grammar.RepeatOp; import net.sf.etl.parsers.internal.term_parser.grammar.Sequence; import net.sf.etl.parsers.internal.term_parser.grammar.Statement; import net.sf.etl.parsers.internal.term_parser.grammar.StringOp; import net.sf.etl.parsers.internal.term_parser.grammar.SyntaxDefinition; import net.sf.etl.parsers.internal.term_parser.grammar.TokenOp; import net.sf.etl.parsers.internal.term_parser.grammar.TokenRefOp; import net.sf.etl.parsers.internal.term_parser.grammar.Wrapper; import net.sf.etl.parsers.internal.term_parser.grammar.ZeroOrMoreOp; /** * <p> * This parser reads ETL grammar expressed in ETL. This is the only grammar that * can be reliably read by this parser. Differently from standard parsers it * breaks on the first error of failed assumption about ETL grammar because it * assumes that ETL grammar itself is correct. All other grammars will be read * by compiled ETL grammar for ETL. * </p> * * <p> * Basically parser reads some correct grammars correctly. If you are lucky the * parser would parse your grammar correctly. If you are unlucky, it will parse * your grammar and will not notice bugs in it so compilation process will fail * in weird way later. * </p> * * <p> * Note that the parser is not optimized and is implemented in simplest possible * way that still works. It does not checks correctness of grammar thoroughly so * giving invalid grammar to it might produce a mess at later phases of * pipeline. * </p> * * <p> * There are the following method types in the source: * </p> * <dl> * <dt>try*</dt> * <dd>These methods examine stream and if head matches specified, they consume * and returns true. Otherwise they do nothing with stream and return false.</dd> * <dt>start*</dt> * <dd>These methods unconditionally start parsing what is specified. And if * stream content does not match expected tokens, the parser fails.</dd> * <dt>end*</dt> * <dd>These methods unconditionally start parsing what is specified. And if * stream content does not match expected tokens, the parser fails.</dd> * <dt>match*</dt> * <dd>These method match current lexer and if token matches specified, return * true of false.</dd> * <dt></dt> * <dd></dd> * </dl> * * @author const */ // NOTE POST 0.2 Implement better error checking and make this parser to reject // non matching grammars. public class BootstrapETLParserLite { /** a logger used by this class to log the problems */ private static final Logger log = Logger.getLogger(DefaultTermParser.class .getName()); /** * stack of properties */ final private Stack<Field> propertyStack = new Stack<Field>(); /** * stack of objects */ final private Stack<EObject> objectStack = new Stack<EObject>(); /** * a phrase parser used by bootstrap parser */ private final PhraseParser parser; /** * Result of parsing */ private Grammar result; /** * A constructor from phrase parser * * @param parser * a parser to use */ public BootstrapETLParserLite(PhraseParser parser) { this.parser = parser; parser.advance(); skipIgnorable(); } /** * Parse grammar. * * @return the first parsed grammar encountered in the file */ public Grammar parse() { final long start = System.currentTimeMillis(); try { while (trySegment()) { if (tryGrammar()) { return result; } else if (tryDoctype()) { // do nothing } else if (match(PhraseTokens.END_SEGMENT)) { // do nothing it is a blank statement } else { fail(); } endSegment(); } return null; } finally { final long end = System.currentTimeMillis(); if (log.isLoggable(Level.FINE)) { log.fine("Bootstrap parser finished, worked for " + (end - start) + "ms."); } } } /** * @return true if doctype has been parsed. */ private boolean tryDoctype() { if (!match("doctype")) { return false; } while (!match(PhraseTokens.END_SEGMENT)) { advance(); } return true; } /** * stop parsing segment */ private void endSegment() { consume(PhraseTokens.END_SEGMENT); } /** * @return true if segment start matched and consumed. */ private boolean trySegment() { return tryToken(PhraseTokens.START_SEGMENT); } /** * @param tk * a token kind to match * @return true if matched. */ private boolean match(PhraseTokens tk) { return parser.current().kind() == tk; } /** * Fail parsing with exception */ private void fail() { throw new IllegalStateException("Some problem at token: " + parser.current()); } /** * Parse grammar * * @return true if it were indeed an grammar. */ private boolean tryGrammar() { if (!match("grammar")) { return false; } startObject(new Grammar()); advance(); do { value(field(Grammar.class, "name")); } while (tryToken(".")); startBlock(); startProperty(field(Grammar.class, "content")); while (trySegment()) { if (tryNamespace()) { // do nothing } else if (tryContext()) { // do nothing } else { // note that other constructs are not supported // because they are not needed by ETL grammar fail(); } endSegment(); } endProperty(); endBlock(); result = (Grammar) endObject(); return true; } /** * @return true if context statement is matched and parsed, false if * statement is not matched. */ private boolean tryContext() { if (!match("context")) { return false; } startObject(new Context()); advance(); while (true) { if (match("default")) { modifier(field(Context.class, "defaultModifier")); } else if (match("abstract")) { modifier(field(Context.class, "abstractModifier")); } else { break; } } value(field(Context.class, "name")); startBlock(); startProperty(field(Context.class, "content")); while (trySegment()) { if (tryDef()) { // do nothing } else if (tryStatement()) { // do nothing } else if (tryDocumentation()) { // do nothing } else if (tryContextImport()) { // do nothing } else if (tryContextInclude()) { // do nothing } else if (tryOp()) { // do nothing } else if (tryAttributes()) { // do nothing } else { fail(); } endSegment(); } endProperty(); endBlock(); endObject(); return true; } /** * @return true if simple operator statement is matched and parsed, false if * statement is not matched. */ private boolean tryOp() { if (!tryToken("op", OperatorDefinition.class)) { return false; } if (match("composite")) { modifier(field(OperatorDefinition.class, "isComposite")); } value(field(SyntaxDefinition.class, "name")); consume("("); value(field(OperatorDefinition.class, "associativity")); if (tryToken(",")) { value(field(OperatorDefinition.class, "precedence")); } if (tryToken(",")) { value(field(OperatorDefinition.class, "text")); } consume(")"); parseDefSyntax(); endObject(); return true; } /** * @return true if context "import" statement is matched and parsed, false * if statement is not matched. */ private boolean tryContextImport() { if (!match("import")) { return false; } startObject(new ContextImport()); advance(); value(field(ContextImport.class, "localName")); consume("="); value(field(ContextRef.class, "contextName")); endObject(); return true; } /** * @return true if context "import" statement is matched and parsed, false * if statement is not matched. */ private boolean tryContextInclude() { if (!match("include")) { return false; } startObject(new ContextInclude()); advance(); value(field(ContextRef.class, "contextName")); if (tryToken("wrapper")) { startProperty(field(ContextInclude.class, "wrappers")); parseWrapperObject(); while (tryToken("/")) { parseWrapperObject(); } endProperty(); } endObject(); return true; } /** * @return true if "statement" statement is matched and parsed, false if * statement is not matched. */ private boolean tryStatement() { if (!match("statement")) { return false; } startObject(new Statement()); advance(); value(field(SyntaxDefinition.class, "name")); parseDefSyntax(); endObject(); return true; } /** * @return true if documentation statement is matched and parsed, false if * statement is not matched. */ private boolean tryDocumentation() { if (!match("documentation")) { return false; } startObject(new DocumentationSyntax()); advance(); value(field(SyntaxDefinition.class, "name")); parseDefSyntax(); endObject(); return true; } /** * @return true if attributes statement is matched and parsed, false if * statement is not matched. */ private boolean tryAttributes() { if (!match("attributes")) { return false; } startObject(new Attributes()); advance(); value(field(SyntaxDefinition.class, "name")); parseDefSyntax(); endObject(); return true; } /** * @return true if context statement is matched and parsed, false if * statement is not matched. */ private boolean tryDef() { if (!match("def")) { return false; } startObject(new Def()); advance(); value(field(SyntaxDefinition.class, "name")); parseDefSyntax(); endObject(); return true; } /** * */ private void parseDefSyntax() { startProperty(field(SyntaxDefinition.class, "syntax")); parseSyntaxBlock(); endProperty(); } /** * parse block of syntax. */ private void parseSyntaxBlock() { startBlock(); while (trySegment()) { if (tryLet()) { // do nothing } else { parseSyntaxExpressionStatement(); } endSegment(); } endBlock(); } /** * parse expression syntax statement */ private void parseSyntaxExpressionStatement() { startObject(new ExpressionStatement()); startProperty(field(ExpressionStatement.class, "syntax")); syntax(); endProperty(); endObject(); } /** * @return true if let statement is matched and parsed, false if statement * is not matched. */ private boolean tryLet() { if (!match("@")) { return false; } startObject(new Let()); advance(); value(field(Let.class, "name")); value(field(Let.class, "operator")); startProperty(field(Let.class, "expression")); syntax(); endProperty(); endObject(); return true; } /** * Parse syntax. Note that it parses all syntax constructs independently of * context. */ private void syntax() { parseChoiceLevel(); } /** * parse choice level operators */ private void parseChoiceLevel() { parseRepeatLevel(); while (tryToken("|")) { wrapTopObject(new ChoiceOp(), field(ChoiceOp.class, "options")); startProperty(field(ChoiceOp.class, "options")); parseRepeatLevel(); endProperty(); endObject(); } } /** * parse operators at repeat level */ private void parseRepeatLevel() { parsePrimaryLevel(); while (true) { if (tryToken("?")) { wrapTopObject(new OptionalOp(), field(RepeatOp.class, "syntax")); endObject(); } else if (tryToken("*")) { wrapTopObject(new ZeroOrMoreOp(), field(RepeatOp.class, "syntax")); endObject(); } else if (tryToken("+")) { wrapTopObject(new OneOrMoreOp(), field(RepeatOp.class, "syntax")); endObject(); } else { break; } } } /** * parse primary level */ private void parsePrimaryLevel() { if (tryToken("^", ObjectOp.class)) { startProperty(field(ObjectOp.class, "name")); parseObjectName(); endProperty(); startProperty(field(CompositeSyntax.class, "syntax")); parseSequence(); endProperty(); endObject(); } else if (match("left") || match("right")) { startObject(new OperandOp()); value(field(OperandOp.class, "position")); endObject(); } else if (tryToken("identifier", IdentifierOp.class)) { parseTokenWrapper(); endObject(); } else if (tryToken("modifier", ModifierOp.class)) { value(field(ModifierOp.class, "value")); parseTokenWrapper(); endObject(); } else if (tryToken("modifiers", ModifiersOp.class)) { parseWrapper(field(ModifiersOp.class, "wrapper")); startProperty(field(ModifiersOp.class, "modifiers")); parseSyntaxBlock(); endProperty(); endObject(); } else if (tryToken("token", TokenOp.class)) { if (tryToken("(")) { value(field(TokenOp.class, "value")); consume(")"); } parseTokenWrapper(); endObject(); } else if (tryToken("float", FloatOp.class)) { parseNumber(); endObject(); } else if (tryToken("integer", IntegerOp.class)) { parseNumber(); endObject(); } else if (tryToken("string", StringOp.class)) { consume("("); consume("quote"); consume("="); value(field(StringOp.class, "quote")); consume(")"); parseTokenWrapper(); endObject(); } else if (tryToken("doclines", DoclinesOp.class)) { parseTokenWrapper(); endObject(); } else if (tryToken("ref", RefOp.class)) { if (tryToken("(")) { value(field(RefOp.class, "name")); consume(")"); } endObject(); } else if (tryToken("block", BlockRef.class)) { if (tryToken("(")) { value(field(ContextOp.class, "context")); consume(")"); } endObject(); } else if (tryToken("expression", ExpressionRef.class)) { if (tryToken("(")) { value(field(ContextOp.class, "context")); if (tryToken(",")) { consume("precedence"); consume("="); value(field(ExpressionRef.class, "precedence")); } ; consume(")"); } endObject(); } else if (tryToken("list", ListOp.class)) { if (!match(PhraseTokens.START_BLOCK)) { value(field(ListOp.class, "separator")); } startProperty(field(CompositeSyntax.class, "syntax")); parseSequence(); endProperty(); endObject(); } else if (match(PhraseTokens.START_BLOCK) || match("%")) { startObject(new Sequence()); startProperty(field(Sequence.class, "syntax")); while (true) { if (tryToken("%", KeywordStatement.class)) { value(field(KeywordStatement.class, "text")); endObject(); } else if (match(PhraseTokens.START_BLOCK)) { parseSyntaxBlock();; } else { break; } } endProperty(); endObject(); } else { fail(); } } /** * parse number declaration */ private void parseNumber() { if (tryToken("(")) { consume("suffix"); consume("="); value(field(NumberOp.class, "suffix")); consume(")"); } parseTokenWrapper(); } /** * parse token wrapper */ private void parseTokenWrapper() { parseWrapper(field(TokenRefOp.class, "wrapper")); } /** * parser wrapper part * * @param property * a property to assign the wrapper */ private void parseWrapper(Field property) { if (tryToken("wrapper")) { startProperty(property); parseWrapperObject(); endProperty(); } } /** * parse wrapper object */ private void parseWrapperObject() { startObject(new Wrapper()); startProperty(field(Wrapper.class, "object")); parseObjectName(); endProperty(); consume("."); value(field(Wrapper.class, "property")); endObject(); } /** * parse sequence object using next block */ private void parseSequence() { startObject(new Sequence()); startProperty(field(Sequence.class, "syntax")); parseSyntaxBlock(); endProperty(); endObject(); } /** * parse object name. */ private void parseObjectName() { startObject(new ObjectName()); value(field(ObjectName.class, "prefix")); consume(":"); value(field(ObjectName.class, "name")); endObject(); } /** * @param tk * token to try * @return true if phrase token matched and consumed, false otherwise */ private boolean tryToken(PhraseTokens tk) { if (match(tk)) { advance(); return true; } return false; } /** * This object start current object in a way that wraps current top object * on the stack into new object. The method used by expression parsers. * * @param object * new object * @param property * a property of new object to which current top will be put. */ @SuppressWarnings("unchecked") private void wrapTopObject(Element object, Field property) { try { final Element po = topObject(); final Field pp = topProperty(); Element v; if (pp.getType() == ArrayList.class) { final java.util.ArrayList<?> l = (java.util.ArrayList<?>) pp .get(po); v = (Element) l.remove(l.size() - 1); } else { v = (Element) pp.get(po); pp.set(po, null); } if (property.getType() == ArrayList.class) { ((java.util.ArrayList<Object>) property.get(object)).add(v); } else { property.set(object, v); } object.ownerObject = v.ownerObject; object.ownerFeature = v.ownerFeature; v.ownerObject = object; v.ownerFeature = property; object.start = v.start; pushObject(object); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException("Field cannot be updated", e); } } /** * @param c * object of this class will be started if token matches * @param string * a string to match. * @return true if token was detected and consumed, false if token did not * match. */ private boolean tryToken(String string, Class<?> c) { if (match(string)) { try { startObject((Element) c.newInstance()); } catch (Exception e) { throw new RuntimeException("Bootstrap parser cannot " + "create an instance: " + c.getCanonicalName()); } advance(); return true; } return false; } /** * @param string * a string to match. * @return true if token was detected and consumed, false if token did not * match. */ private boolean tryToken(String string) { if (match(string)) { advance(); return true; } return false; } /** * Read modifier * * @param modifierProperty * a property where modifier should be put */ private void modifier(Field modifierProperty) { startProperty(modifierProperty); startObject(new Modifier()); value(field(Modifier.class, "value")); endObject(); endProperty(); } /** * @return true if namespace statement is matched, false if statement is not * matched. */ private boolean tryNamespace() { if (!match("namespace")) { return false; } startObject(new Namespace()); advance(); if (match("default")) { modifier(field(Namespace.class, "defaultModifier")); } value(field(Namespace.class, "prefix")); consume("="); value(field(Namespace.class, "uri")); endObject(); return true; } /** * Consume token that matches string or fail parsing * * @param string * a string to match */ private void consume(String string) { if (match(string)) { advance(); } else { fail(); } } /** * End property scope */ private void endProperty() { propertyStack.pop(); } /** * Start property. All objects created before property ends will be * considered belonging to this property * * @param property * a property to start */ private void startProperty(Field property) { propertyStack.push(property); } /** * end block */ private void endBlock() { consume(PhraseTokens.END_BLOCK); } /** * start parsing block unconditionally */ private void startBlock() { consume(PhraseTokens.START_BLOCK); } /** * consume phrase token of specified type and advance * * @param token * a token to consume * * */ private void consume(PhraseTokens token) { if (match(token)) { advance(); } else { fail(); } } /** * @return pop object */ private Element endObject() { final Element object = (Element) objectStack.pop(); object.end = parser.current().start(); return object; } /** * Consume value and put it to property * * @param featureId * a filed to put consumed value */ @SuppressWarnings("unchecked") private void value(Field featureId) { try { final Class type = featureId.getType(); final EObject top = topObject(); if (type.isEnum()) { final String text = text(); Enum value = null; for (final Object o : type.getEnumConstants()) { final Enum e = (Enum) o; // note that line below works only for single-word enums if (text.equalsIgnoreCase(e.name())) { value = e; break; } } if (value == null) { throw new RuntimeException("No constant with name " + text() + " in enum " + type.getCanonicalName()); } featureId.set(top, value); } else if (type == String.class) { featureId.set(top, text()); } else if (type == Integer.class || type == int.class) { featureId.set(top, LiteralUtils.parseInt(text())); } else if (type == ArrayList.class) { Type gType = featureId.getGenericType(); if (gType instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) gType; if (pt.getActualTypeArguments()[0] == String.class) { ((ArrayList<String>) featureId.get(top)).add(text()); } else { throw new RuntimeException("Unsupported argument type:" + featureId); } } else { throw new RuntimeException("Unsupported argument type:" + featureId); } } else { throw new RuntimeException("Unsupported field type:" + featureId); } advance(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException( "Field cannot be accessed: " + featureId, e); } } /** * @return text of current token */ private String text() { if (!parser.current().hasToken()) { fail(); } return parser.current().token().text(); } /** * @return object at top of the stack */ private Element topObject() { return (Element) objectStack.peek(); } /** * * @param object * object to start */ private void startObject(Element object) { pushObject(object); object.start = parser.current().start(); } /** * @param object */ @SuppressWarnings("unchecked") private void pushObject(Element object) { final Field sf = topProperty(); try { if (sf != null) { final Element top = topObject(); object.ownerObject = top; object.ownerFeature = sf; Class<?> type = sf.getType(); if (type == ArrayList.class || type == LinkedList.class) { ((List<EObject>) sf.get(top)).add(object); } else { sf.set(top, object); } } objectStack.push(object); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException("Field cannot be accessed: " + sf, e); } } /** * @return currently top property on the stack or null if stack is empty */ private Field topProperty() { return propertyStack.size() > 0 ? propertyStack.peek() : null; } /** * Try to match text * * @param text * a text to match * @return true if text matches */ private boolean match(String text) { return parser.current().hasToken() && text.equals(parser.current().token().text()); } /** * Move to new phrase token in the stream. * * @return true if parser actually moved. */ private boolean advance() { if (phraseKind() == PhraseTokens.EOF) { return false; } parser.advance(); skipIgnorable(); return true; } /** * skip ignorable and control tokens */ private void skipIgnorable() { ignoreLoop: while (true) { switch (phraseKind()) { case IGNORABLE: case CONTROL: break; case SIGNIFICANT: if (match(Tokens.DOC_COMMENT)) { break; } else { break ignoreLoop; } default: break ignoreLoop; } parser.advance(); } } /** * Tries to match token to specific kind * * @param kind * a kind to match * @return true if matched. */ private boolean match(Tokens kind) { return parser.current().hasToken() && parser.current().token().kind() == kind; } /** * @return phrase kind for current token */ private PhraseTokens phraseKind() { return parser.current().kind(); } /** * a cache of fields. note that strings are interned in this class, so it * does not make sense to use plain hash map here. */ HashMap<Class<?>, IdentityHashMap<String, Field>> fieldCache = new HashMap<Class<?>, IdentityHashMap<String, Field>>(); /** * Get a field from class * * @param c * class to example * @param name * a name to get * @return the field */ private Field field(Class<?> c, String name) { try { IdentityHashMap<String, Field> classFields = fieldCache.get(c); if (classFields == null) { classFields = new IdentityHashMap<String, Field>(); fieldCache.put(c, classFields); } Field rc = classFields.get(name); if (rc == null) { rc = c.getField(name); classFields.put(name, rc); } return rc; } catch (Exception e) { throw new RuntimeException("Unable to find field " + name + " in class " + c.getCanonicalName(), e); } } }